一、无参属性
对于字段,强烈建议将所有的字段都设为private。如果允许用户或类型获取或设置状态信息,就公开一个针对该用途的方法。封装了字段访问的方法通常称为访问器(accessor)方法。访问器方法可选择对数据的合理性进行检查,确保对象的状态永远不被破坏。如下代码:
private sealed class Employee { private String m_Name; private Int32 m_Age; public String GetName(){ return m_Name; } public void SetName(String value){ m_Name = value; } public Int32 GetAge(){ return m_Age; } public void SetAge(Int32 value){ if (value <= 0) throw new ArgumentOutOfRangeException("value", "must be >0"); m_Age = value; } }
想这样的数据封装有两个缺点。第一:不得不实现额外的方法;第二、用户必须调用方法。
private sealed class Employee { private String m_Name; private Int32 m_Age; public String Name { get { return (m_Name); } set { m_Name = value; } // 关键字'value' 总是代表新值 } public Int32 Age { get { return (m_Age); } set { if (value <= 0) // 关键字'value' 总是代表新值 throw new ArgumentOutOfRangeException("value", "must be >0"); m_Age = value; } } }
于是就可以这样调用:
Employee emp = new Employee(); emp.Name = "Jeffrey Richter"; emp.Age = 45; Console.WriteLine("Employee info: Name = {0}, Age = {1}", emp.Name, emp.Age);
可将属性想象成智能字段,即背后有额外逻辑的字段。CLR支持静态、实例、抽象和虚属性。属性可以使用任意可访问性修饰符修饰。
private sealed class Employee { //这是一个自动实现属性 public String Name {get;set;} }
2.合理定义属性
属性和字段的比较:
Employee e = new Employee[()] { Name = "Jeff", Age = 45 }
string s = new Employee() {Name = "Jeff", Age = 45}.ToString().ToUpper();
//定义一个类型,后再它的一个实例,并初始化它的属性 var o1 = new { Name = "Jeff", Year = 1964 }; Console.WriteLine("Name={0}, Year={1}", o1.Name, o1.Year);
第一行代码创建了一个匿名类型,没有在new 关键字后制定类型名称,所以编译器会为我自动创建一个类型名称,而且不会告诉我这个名称是什么(这正是匿名类型一词的由来),但编译器是知道的。虽然我不知道变量o1声明的是什么类型,但可以利用C#的"隐式推断类型局部变量"功能(var)。
编译器支持用另外两种语法声明匿名类型中的属性,它根据变量推断出属性名和类型:
String Name = "Grant"; DateTime dt = DateTime.Now; // 有两个属性的一个匿名类型 // 1. String Name 属性设为"Grant" // 2. Int32 Year 属性设为dt中的年份 var o2 = new { Name, dt.Year };
在这个例子中,编译器判断第一个属性名为Name。由于Name是一个局部变量的名称,所以编译器将属性类型设为与局部变量相同的类型:String。对于第二个属性,编译器使用字段/属性的名称:Year。Year是DateTime类的一个Int32属性,所以匿名类型中的Year属性也是一个Int32。
//这是最简单的 public class Tuple<T1> { private T1 m_item1; public Tuple(T1 item1) { m_Item1 = item1;} public item1 { get { retuen m_Item1; } } }
和匿名类型相似,一旦创建好了一个Tuple,他就不可变了(所有属性都只读)。Tuple类还提供了CompareTo,Equals,GetHashCode和ToString方法,另外还提供了一个Size属性。除此之外,所有Tuple类型都实现了IstruralEquatable,IstructuralComparable和IComparable接口,所以可以比较两个Tuple对象。
internal sealed class BitArray { // 容纳了二进制位的私有字节数组 private Byte[] m_byteArray; private Int32 m_numBits; // 下面的构造器用于分配字节数组,并将所有位设为 0 public BitArray(Int32 numBits) { // 先验证实参 if (numBits <= 0) throw new ArgumentOutOfRangeException("numBits must be > 0"); // 保留位的个数 m_numBits = numBits; // 为位数组分配字节 m_byteArray = new Byte[(m_numBits + 7) / 8]; } // 下面是索引器(有参属性) public Boolean this[Int32 bitPos] { // 下面是索引器的get访问器方法 get { // 先验证实参 if ((bitPos < 0) || (bitPos >= m_numBits)) throw new ArgumentOutOfRangeException("bitPos", "bitPos must be between 0 and " + m_numBits); // 返回指定索引处的位的状态 return ((m_byteArray[bitPos / 8] & (1 << (bitPos % 8))) != 0); } // 下面是索引器的set访问器方法 set { if ((bitPos < 0) || (bitPos >= m_numBits)) throw new ArgumentOutOfRangeException("bitPos", "bitPos must be between 0 and " + m_numBits); if (value) { // 将指定索引处的位设为true m_byteArray[bitPos / 8] = (Byte) (m_byteArray[bitPos / 8] | (1 << (bitPos % 8))); } else { // 将指定索引处的位设为false m_byteArray[bitPos / 8] = (Byte) (m_byteArray[bitPos / 8] & ~(1 << (bitPos % 8))); } } } }
BitArray类的调用也非常简单:
private static void BitArrayTest() { // 分配含有14个位的bitArray数组 BitArray ba = new BitArray(14); // 调用set访问器方法,将编号为偶数的所有为设为true for (Int32 x = 0; x < 14; x++) { ba[x] = (x % 2 == 0); } // 调用get访问器方法显示所有为的状态 for (Int32 x = 0; x < 14; x++) { Console.WriteLine("Bit " + x + " is " + (ba[x] ? "On" : "Off")); } }
三、调用属性访问器方法时的性能
四、属性访问器的可访问性
我们有时希望为get访问器方法指定一种可访问性,为set访问器方法指定另一种可访问性。如下:
public class SomeType { private String m_name; public String Name { get { return m_name;} protected set { m_name = value;} } }
定义一个属性时,如果两个访问器方法需要具有不同的可访问性,C#语法要求必须为属性本身指定限制最不大的那一种可访问性。然后,在两个访问器中,只能选择一个来应用限制较大的那一种可访问性。如前面例子中,属性本身声明为public,set访问器方法声明为protected(限制比public大)。
五、泛型属性访问器方法