C Sharp/属性
属性(property)是一种用于访问对象或类的内容的机制。可以像使用公共数据成员一样使用属性,但实际上属性是称为“访问器”的一种特殊方法。
属性(property)是一种"高级字段”,它可能带有一个 getter 和一个 setter,它们保护属性的值,使之不会被外部胡乱篡改。
和字段相比,属性实现了对成员的封装。
无参属性
[编辑]class A
{
private int c;
public int getC()
{
return c;
}
public void setC(int value)
{
c = value;
}
}
在这里的私有字段称为支持字段(Backing Field)。
不过,这样做有两个明显缺点,一是必须手打这些代码;二是在访问属性时,必须调用方法,而不能直接使用点号加属性名。
CLR 提供了称为属性的机制,解决了这两个缺点。下面的写法是经过简化了的写法:
private int c { get; set; }
这样创建的属性叫做自动实现的属性。可以直接通过 A.c 访问属性,而非使用 A.getC 和 A.setC 方法了。
实际上,无参属性仅仅是语法糖。通过编译之后使用 iladsm 查看,我们可以发现,编译器自动为我们生成了 get_c 和 sct_c 方法,以及一个支持字段k_BackingField。
只读和只写属性
[编辑]可以通过将 get 或 set 设置为 private 获得只读和只写属性。
例如,如果 set 是私有的,则属性就是只读的。不过,属性的值仍然可以被类型内部的成员修改:
public int c { get; private set; }
从 C# 6 开始,允许省略 set 获得真正具有不变性的属性:
public int c { get; }
此时这个字段就真正地具有了不变性,当你初始化了这个字段的值之后,就再也无法更改它的值。
带有逻辑的属性
[编辑]通过为属性的 get 和 set 中加入代码,可以控制属性的取值范围。例如,要实现一个永远为非负整数的属性:
private int age;
public int Age
{
get
{
return age;
}
set
{
if (value < 0)
{
throw new ArgumentOutOfRangeException("Age", value, "Age必须大于等于0");
}
age = value;
}
}
在这段代码中,关键字 value 代表赋值时传入的新值。
有参属性
[编辑]有参属性的 get 方法支持传入一个或更多参数,set 方法支持传入两个或更多参数。
C# 中的有参属性又叫索引器,顾名思义,它是重载[]操作符的一种方式。
虽然有参属性(索引器)很少被使用,不过,它有一个常用场景是这样的:一个类的成员包括一个集合。比如,记录每天 24 小时温度的 DayTemperature 类,具有一个长度为 24 的 double 集合成员 Temperatures。当拿到这个类的一个实例 d 时,访问它的成员需要 d.Temperatures[6](代表早上 6 点的温度)。如果对这个类实现有参属性,可以直接使用 d[6] 获得相同的值,省去了集合成员名称 Temperatures。这种表示法不仅简化了客户端应用程序的语法,还使其他开发人员能够更加直观地理解类及其用途。
索引器至少要定义一个访问方法(即 get 或 set)。
class Program
{
static void Main(string[] args)
{
var d = new DayTemperature();
d.temperature[0] = 20.5;
d.temperature[1] = 22;
//使用类索引器访问
Console.WriteLine(d[1]); //22
Console.ReadKey();
}
}
class DayTemperature
{
public double[] temperature = new double[24];
//类的索引器
public double this[int index]
{
get
{
//检查索引范围
if (index < 0 || index >= temperature.Length)
{
return -1;
}
else
{
return temperature[index];
}
}
set
{
if (!(index < 0 || index >= temperature.Length))
{
temperature[index] = value;
}
}
}
}
属性的意义
[编辑]通过属性的封装,保留了它与外部交互的能力,又实现了一种可靠的读写机制。私有的字段、属性和方法保护了这些成员,使它们不会被外界调用,因为这是外界无需知道的信息。
通过封装,类型只需要向外部提供它应该知道的信息,否则就会出现“你知道的太多了”这种情形。