接上一篇.net框架读书笔记---虚方法,本章主题属性
CLR提供了两种属性:无参属性和含参属性,在C#中,前者通常被称为属性(property),后者被称为索引器(indexer)。
一、无参属性
许多类型都定义了可被外部代码读取或者改变的状态信息。如下
namespace PropertyAndIndexer
{
class Program
{
static void Main( string [] args)
{
// 可以这样子设置和读取它的状态信息
Employee e = new Employee();
e.Name = " heaiping " ;
e.Age = 24 ;
Console.WriteLine(e.Name);
}
}
public class Employee
{
public string Name; // 雇员姓名
public int Age; // 雇员年龄
}
}
建议大家不要像上面那样子做,面向对象的一个原则是数据封装(data encapsulation)。这意味着我们不应该将类型的字段以公有的方式提供给外界,因为外界代码很容易错误的使用这些字段,从而破坏对象的状态。比如下面:
e.Age = - 100 ;//年龄这么可能是-100呢??这么办
还有一个原因支持对一个字段的访问进行封装。例如,可能希望在访问一个字段的同时执行一些额外的操作。
基于上述理由,应该将所有的字段的访问限制为私有方式,或者至少应该是保护的---永远不要设为公有方式。然后再以方法的形式让用户读取或者设置对象的状态。方法访问器(accessor method)。
public class Employee
{
private string Name; // 雇员姓名
private int Age; // 雇员年龄
public string GetName()
{
return Name;
}
public void SetName( string value)
{
this .Name = value;
}
public int GetAge()
{
return Age;
}
public void SetAge( int value)
{
if (value < 0 )
{
throw new ArgumentOutOfRangeException( " Age must be greater than or equal to 0 " );
}
this .Age = value;
}
}
这样子便可以避免将age设置为-100的问题,但是上面的代码有一定的缺点,一,需要编写许多代码,二,类型的用户必须调用方法而不是简单的应用字段名称。然而,CLR提供了一种称作为属性的机制解决了以上缺点。下面用属性实现上面的相同功能。
public class Employee
{
private string _Name; // 雇员姓名
private int _Age; // 雇员年龄
public string Name
{
get
{
return _Name;
}
set
{
_Name = value;
}
}
public int Age
{
get
{
return _Age;
}
set
{
if (value < 0 )
{
throw new ArgumentOutOfRangeException( " Age must be greater than or equal to 0 " );
}
_Age = value;
}
}
}
属性完成,CLR支持静态属性、实例属性和虚属性,另外属性可以标记为任何的访问限定符。
每个属性都有一个名称和类型(不能为void),属性不能被重载(两个属性名称相同,但类型不同),当定义一个属性时,我们一般会指定一个get方法和set方法,也可以去掉set方法来定义一个只读属性、或者去掉一个方法来定义一个只写属性。
因为访问属性和访问字段有着相同的语法表达,所以只有那些执行时间比较短的操作,我们才应该使用属性。对于那些操作比较费时的操作应该封装为方法。
对于简单的get和set方法,jit编译器会将代码内联处理,这样使用属性不会再有运行时性能损失(相对于字段)。
二、含参属性(索引器)
在C#中,含参属性(索引器)可以用类似于数组的语法来访问,可以将索引器看作是重载[]操作符的一种方式。看下面例子。
class Program
{
static void Main( string [] args)
{
BitArray ba = new BitArray( 14 );
// 调用set
for ( int x = 0 ; x < 14 ; x ++ )
{
ba[x] = (x % 2 == 0 );
}
// 调用get
for ( int x = 0 ; x < 14 ; x ++ )
{
Console.WriteLine(ba[x]);
}
}
}
public class BitArray
{
private byte [] byteArray;
private int numBits;
public BitArray( int numBits)
{
if (numBits < 0 )
{
throw new ArgumentOutOfRangeException( " numBit must be >0 " );
}
this .numBits = numBits;
byteArray = new byte [(numBits + 7 ) / 8 ];
}
public bool this [ int bitPos]
{
get
{
if (bitPos < 0 || bitPos >= numBits)
{
throw new IndexOutOfRangeException();
}
return (byteArray[bitPos / 8 ] & ( 1 << (bitPos % 8 ))) != 0 ;
}
set
{
if (bitPos < 0 || bitPos >= numBits)
{
throw new IndexOutOfRangeException();
}
if (value)
{
byteArray[bitPos / 8 ] = ( byte )
(byteArray[bitPos / 8 ] & ( 1 << (bitPos % 8 )));
}
else
{
byteArray[bitPos / 8 ] = ( byte )
(byteArray[bitPos / 8 ] & ~ ( 1 << (bitPos % 8 )));
}
}
}
}
所有的索引器都必须至少有一个参数,也可以有多个。这些参数(以及返回值)可以为任何类型。
毕竟增加属性需要额外的代码,有人会想,我先使用公有字段,然后在需要的时候将公有字段在不改变名字的前提下修改为属性。这样子可以吗,不可以,为什么,请看effective C#条款1(11页):
虽然属性和数据成员在源代码层次上是兼容的,但是在二进制层次上却不兼容。这意味着如果将一个公有数据成员改为公有属性,那么我们必须重新编译所有使用该公有数据成员的C#代码,因为它改变了二进制的兼容性。如果这样的程序已经被部署。那么升级它们的工作将变的非常麻烦。