一、属性的定义和特征
1.属性的定义
属性是表示类或类的实例中的一个数据项的成员:它提供灵活的机制来读取、编写或计算某个私有字段的值。可以像使用公共数据成员一样使用属性,但实际上它们是称作“访问器”的特殊方法。这使得可以轻松访问数据,此外还有助于提高方法的安全性和灵活性。
2.属性的特征
1).属性是命名的类成员,它有类型,可以被赋值和读取。
2).与字段不同,属性是一个函数成员;它本身没有任何存储,不为数据存储分配内存;它执行代码。
3).属性有两个匹配的、称为访问器的方法:set访问器为属性赋值,get访问器从属性中获取值。
4).在使用属性时,属性访问器被隐式调用:当为属性赋值时,set访问器被隐式调用;当从属性中读取值时,get访问器被隐式调用;不能显式调用访问器。
3.属性的示例
1 class TimePeriod 2 { 3 // 声明一个后备字段 4 private double seconds; 5 6 // 声明一个doub类型的属性,在属性内部将小时转换为秒 7 public double Hours 8 { 9 // get访问器:返回属性类型的值 10 get { return seconds / 3600; } 11 // set访问器:为属性赋值 12 set { seconds = value * 3600; } 13 } 14 } 15 16 class Program 17 { 18 static void Main(string[] args) 19 { 20 TimePeriod t = new TimePeriod(); 21 22 // 为属性赋值,隐式调用set访问器. 23 t.Hours = 24; 24 25 // 读取属性值,隐式调用get访问器. 26 System.Console.WriteLine("Time in hours: " + t.Hours); 27 } 28 }
二、属性访问器
属性的访问器就是两个方法,set访问器和get访问器有预定义的语法和语义。我们按照预定义的语法声明的属性,经过编译器编译之后,会自动分别为set访问器和get访问器生成相应的方法。编译器生成的方法经过反编译可以看得一清二楚。下面是我用Reflector反编译上例中的TimePeriod类之后,它的Hours属性的结构:
可以看到,编译器为Hours属性生成了一个返回值类型为void的方法名为set_Hours的方法,一个返回值类型为double的get_Hours方法。
1.set访问器
从上图已经可以看到了属性内部的结构。这里再来进一步分析属性所生成的setHours方法内部的秘密。经过反编译之后的set_Hours方法的内部代码如下:
1 public void set_Hours(double value) 2 { 3 this.seconds = value * 3600.0; 4 }
从上面代码中我们可以分析得出set访问器的特征:
1).set访问器有一个单独的、隐式的值参数,参数名称为value,参数类型与属性的类型相同。
2).set访问器的返回类型为void。
2.get访问器
下面是get_Hours方法的内部实现代码:
1 public double get_Hours() 2 { 3 return (this.seconds / 3600.0); 4 }
get访问器的特征:
1).get访问器没有参数。
2).get访问器有一个与属性类型一致的返回值类型。
3).get访问器的所有执行路径必须有一条return语句,返回属性类型的值。
三、属性和关联字段
1.属性通常和字段关联,字段存储实际的数据,而属性为字段提供了一种访问渠道。通常的做法是:在类中将字段声明为private,并且为字段声明一个public属性以提供受控的从类的外部对字段的访问。和属性关联的字段称为“后备字段"。
2.属性和后备字段有两种命名约定:
第一种:后备字段采用camel命名法,而属性采用字段的Pascal命名版本。
第二种:属性命名不变,仍然使用Pascal命名法,字段采用"下划线+camel"的形式。
3.下面是另一个示例:该示例声明了一个名为Student的类,该类的每个属性都有一个采用了“_+camel”命名的后备字段。其中类成员声明按照:Fields—>Methods->Properties的顺序进行。
1 using System; 2 namespace MyClass 3 { 4 class Student 5 { 6 // Fields 7 private string _name; 8 private string _sex; 9 private int _age; 10 private float _chinese; 11 private float _math; 12 private float _english; 13 14 // Constructor 15 public Student() 16 { 17 18 } 19 20 // Methods 21 public void SayHello() 22 { 23 System.Console.WriteLine("我叫{0},今年{1}岁了,是{2}同学", this.Name, this.Age, this.Sex); 24 } 25 26 /// <summary> 27 /// 总成绩 28 /// </summary> 29 /// <returns></returns> 30 public float Sum() 31 { 32 return this.Chinese + this.Math + this.English; 33 } 34 35 /// <summary> 36 /// 平均成绩 37 /// </summary> 38 /// <returns></returns> 39 public float Average() 40 { 41 return (this.Chinese + this.Math + this.English) / 3f; 42 } 43 44 // Properties 45 public string Name 46 { 47 get { return this._name; } 48 set { this._name = value; } 49 } 50 51 public string Sex 52 { 53 get { return _sex; } 54 set { _sex = value; } 55 } 56 57 public int Age 58 { 59 get { return _age; } 60 set { _age = value; } 61 } 62 63 public float Chinese 64 { 65 get { return _chinese; } 66 set { _chinese = value; } 67 } 68 69 public float Math 70 { 71 get { return _math; } 72 set { _math = value; } 73 } 74 75 public float English 76 { 77 get { return _english; } 78 set { _english = value; } 79 } 80 } 81 }
四、只读属性和只写属性
1.只读属性:只有get访问器的属性称为只读属性。该属性使得只能将一项数据从类或类的实例传出,而不能有其它访问。
public string Name { get { return this._name; } }
2.只写属性:只有set访问器的属性称为只写属性。该属性值允许数据从类的外部传入类。
1 public string Name 2 { 3 set { this._name = value; } 4 }
3.属性必须有至少一个访问器,要么既可读、又可写,要么提供一个。不然编译器会报错。下面是我将Name属性的set访问器和get访问器都去掉之后报的一条错误消息:
五、属性与公共字段的比较
属性结合了字段和方法的多个方面。对于对象,属性显示为字段,访问属性需要与访问字段相同的语法。对于类的实现者(类本身),属性是一个或两个代码块,表示一个 get 访问器和/或一个 set 访问器。访问器可以对数据的访问提供一些限制和流程控制。
以下两点表明在实际编码中使用属性更好、更灵活:
1.字段是数据成员,只有存储能力,没有处理数据能力;而属性是函数成员,可以通过访问器对数据的输入输出进行控制。
2.程序编译之后,字段和属性的语义不同。
六、自动实现属性
1.从C#3.0中增加了自动实现属性(automatically implemented property),自动属性就是只声明属性而不声明后备字段,编译器会自动创建隐式的后备字段,并且自动与set和get访问器相关联。当属性的访问器中不需要其他逻辑时,自动实现的属性可使属性声明更加简洁。实际开发中,很多都是使用自动属性的。
2.自动实现属性的特征:
1).不必声明后备字段,编译器会根据属性的类型自动生成隐式的后备字段来存储数据。
2).不能提供访问器的方法体,set访问器和get访问器的方法体被一个分号代替。这也是符合逻辑的,因为都不知道后备字段是什么,当然无法对它提供访问控制了,这一切都交给了编译器。
3).必须同时提供读写属性,缺一不可。
3.示例
1 // 自动实现属性 2 public string Name 3 { 4 // 只有简单的get;和set;没有提供方法体 5 get; 6 set; 7 }
4.自动实现属性的内部实现
1 // 由编译器生成的set访问器 2 [CompilerGenerated] 3 public void set_Name(string value) 4 { 5 this.<Name>k__BackingField = value; 6 } 7 8 // 编译器生成的get访问器 9 [CompilerGenerated] 10 public string get_Name() 11 { 12 return this.<Name>k__BackingField; 13 }
通过上面的代码可以看出,编译器为自动属性Name生成了名为“”的后备字段,并且生成了set_Name和get_Name两个访问器。
七、静态属性
1.静态属性与声明方式
可以在属性声明的类型前面加上static关键字声明静态属性。静态属性的特征如下:
1).不能访问类的实例成员。
2).不管类是否有实例,静态属性都是存在的。
3).访问方式:从类的内部访问时,与访问其他类成员一样;从类的外部访问时,需要使用“类型名.成员名”的方式。
2.示例
1 class Trivial 2 { 3 // 声明一个静态自动实现属性 4 public static int MyVale { get; set; } 5 6 // 声明一个实例方法 7 public void PrintValue() 8 { 9 // 从类内部的实例方法访问静态属性:与访问其他成员一样。 10 Console.WriteLine("MyValue = {0}", MyVale); 11 } 12 } 13 class Program 14 { 15 static void Main(string[] args) 16 { 17 #region MyRegion 18 TimePeriod t = new TimePeriod(); 19 20 // 为属性赋值,隐式调用set访问器. 21 t.Hours = 24; 22 23 // 读取属性值,隐式调用get访问器. 24 System.Console.WriteLine("Time in hours: " + t.Hours); 25 #endregion 26 27 // 从类的外部访问静态属性:使用"类名.属性名"的访问方式 28 Trivial.MyVale = 10; 29 Console.WriteLine(Trivial.MyVale); 30 } 31 }