一、为什么需要属性
属性:属性是这样的一个成员,它提供灵活的机制来读取、编写或计算某个私有字段的值。可以像使用公共数据成员一样使用属性,但实际上它们称任“访问器”的特殊方法。这使得可以轻松的访问数据,此外还有助于提高方法的安全性和灵活性。--摘自MSDN
为什么需要属性:
1、 面向对象设计和编程重要的原则之一就是数据封装,它意味着类中所有字段都不应该被类外部直接访问,因为这样很容易写不不恰当的使用字段的代码,从而破坏对象,就是说直接操作对象会对类造成不安全的因素
例如A类中有个Int类型字段Age。Age=-8; 显然类本身对于这个字段的写入没有限制,但年龄不应该为负数,所以我们期望有这么一个机制,对于字段的设置时,有一个额外
的验证机制(Age>0),这时,属性是一个很好的选择.
2、 对于字段的操作,本身是没有读写的限制的,当然可以以readonly字段限制字段只读属性,但是无法提供一个只写的机制(当然这样本身是没有意义),此时,属性又是一个很好的选择
3、字段可能是一个逻辑字段,它的值不由内存中的字节表示,而是通过某个算法来计算得出。
二、属性
C#中,大部分的属性都为无参属性,无参,顾名思义,就是没有参数的一种属性,之所以要区分,是由于C#中还有另一种属性,有参属性又作索引,对于大部分的集合类中一般都有索引.
1、有参属性
public class Person
{
private string name;
private Int32 age;
public string Name
{
get { return name; }
set
{
name = value;//value总是代表新值
}
}
public Int32 Age
{
get { return age; }
set
{
if (value < 0)//安全性检查
{
throw new ArgumentOutOfRangeException("the value must be greater than 0");
}
age = value;
}
}
}
Person p = new Person();
p.Name = "LPF";
p.Age = -9;//error
属性通过get获取私有字段,通过set来写入字段,value永远是一个最新的值,有它的类型是定义属性的类型,我们可以把属性的写入和获取当作调用了一个get方法和set方法,其实编译器在最后的托管集里面就是生成了一个get方法和set方法,我们可以用反编译一下生成的exe即可发现,如下图:
所以,我们知道,get和set语法只是提供一个良好的操作方式,但对于最终的实现方式,还是调用了get和set方法,对于get和set方法名的命名,编译器是以“get_属性名”和“set_属性名”来生成各自对应的方法。
2、有参属性(索引器)
对于一般属性(无参属性)索引(有参属性),它的get访问器方法需要接受一个或者多个参数,set访问器方法需要接受两个或者多个参数。
例2:
public class StringArray
{
private string[] str = { "C#", "C++", "Java" };
public string this[int index]
{
get
{
if (index > 2 && index < 0)
{
throw new IndexOutOfRangeException("");
}
return str[index];
}
set
{
if (index > 2 && index < 0)
{
throw new IndexOutOfRangeException("");
}
str[index] = value;
}
}
}
StringArray s = new StringArray();
Console.WriteLine(s[0]);
s[0] = "SQL";
Console.WriteLine(s[0]);
前面我们知道无参属性在编译器生成托管代码时会生成对应的set和get方法,对于有参属性,同样会生成对应的方法,但其方法名与生成无参属性方法名的方式不一样,对于索引,编译器在索引器名称之前添加set_和get_前缀,从而自动生成这些方法的名称,由于C#索引器语法不允许人为指定一个索引器的名称,所以C#不得不为访问器默认选择一个名称,他们选择了Item,因此编译器生成的方法名就是get_Item和set_Item,如下图:
如果开发人员想设定索引器在托管集上生成的方法名不想用编译器生成的Item时(如果类型设计的索引器要由其他语言编写的代码访问时,就有可能要改更索引器的get和set访问器方法设定的的默认名称Item),C#允许向索引器就用System.Runtime.CompilerServices.IndexerNameAttribute定制attribute来重命名这些方法
例如:
[System.Runtime.CompilerServices.IndexerName("Index")]
public string this[int index]{…}
3、 自动属性
如果只是为封装一个支持字段的属性,C#提供了一个简单的语法,称做自动实现的属性,下面的Name就是一个例子
public class AutoProperty
{
public string Name { get; set; }
}
如果声明一个属性,而不提供get/set的实现,则C#会自动为你声明一个私有字段,在本例中,私有字段的类型是string,也就是属性的类型,编译器还会自动实现get_Name和set_Name方法,分别返回和设置字段中的值,我们可以用Reflector工具来反编译一下生成的exe,如下图:
图中<Name>k_BackingField这个字段就是编译器自动声明的一个私有字段,并且这个字段的名称并不友好,为的就是防止与类中本身存在的字段重名。
注意:使用自动属性时,必须显示要求get/set两个访问器都可用,不能只get或者只set
优点:如果仅仅只是封装一个支持字段的属性,可以减少代码的输入量
缺点:
1、 字段显示声明语法可能包含初始化值,但自动属性的支持字段是没有办法在声明时初始化值
2、 运行时序列化会将字段持久存诸到序列化流中,由于自动属性的字段名称是编译器决定的,而且每次编译代码,生产的字段名称有可能不同,这样一来,任何一个类中含有自动属性,则这个类就不能对这个类的实例进行反序列化了。
3、 调试时无法添加断点
4、 对字段的设置无法进行安全性验证
三、属性特点
1、属性的重载
无参属性不能重载:
无参属性不能重载,也就是说,不能定义名称相同、类型不同的的两个无参属性,由于属性的本质是一个方法,因此对于讨论属性的重载是有一定意义的。
无参属性为什么不可以重载?我们以无参属性2.1节中的例1来讲,string Name属性,编译器会在托管集上生成的两个方法 string get_Name(),假使现在Person类里新增一个与Name重名的StringBuilder Name属性,那么最终它在托管堆上生成的方法是 StringBuilder get_Name(),比较string get_Name()与StringBuilderget_Name() 显然它们不符合重载的条件(函数的重载不应该以返回类型做为区别,因为编译器无法判断调用的意图)
所以从这个角度去理解,很明显无参属性不能重载
例如下面的代码不能编译通过:
public class A
{
private string name;
private StringBuilder new_name;
public string Name
{
get { return name; }
set { name = value; }
}
public StringBuilder Name //ERROR
{
get { return new_name; }
set { new_name = value; }
}
}
注意:有人可能注意到,对于get方法来讲不满足重载条件,但是对于set方法来讲,是可以满足的,比如,如果两个重名Name属性的类型不一样并且设置成只写(只有set访问器没有get访问器),那么Name生成的set_Name方法:void set_Name(string s)和void set_Name(StringBuilder s),那么对于这个属性不就是可以重载了么?(因为它生成的方法满足重载)事实上,这种做法也是无法编译通过的,至于为什么,我想微软只想让开发人员把注意全部集中在属性名称本身,对于属性本身的意义来讲,如果有两个一样名称的属性,会造成歧义,在代码的编写过程中容易出错,因此干脆就强制不能重载,这只是我的个人理解
所以下面的代码同样无法编译通过:
public class A
{
private string name;
private StringBuilder new_name;
public string Name
{
set { name = value; }
}
public StringBuilder Name//ERROR
{
set { new_name = value; }
}
}
有参属性(索引器)可以重载:
C#中,一个类型可以定义多个索引器,只要索引器的参数集不同即可,以第二节中的例2来分析,string this[int index],这个属性编译器在托管集中生成的两个方法:string get_Item(int index)和void set_Item(intindex,string val), 此时如若新增一个索引器 string this[string key],假使编译器在托管集中会新增两个方法:stringget_Item(string key)和void set_Item(string key,string val),以前面分析无参属性的过程发现,对于索引器是允许重载的(只要索引器的参数集不同)
所以下面的代码是可以编译通过的:
public class StringArray
{
private string[] str = { "C#", "C++", "Java" };
public string this[int index]
{
get
{
if (index > 2 && index < 0)
{
throw new IndexOutOfRangeException("");
}
return str[index];
}
set
{
if (index > 2 && index < 0)
{
throw new IndexOutOfRangeException("");
}
str[index] = value;
}
}
public string this[string key] //重载,参数集类型不同
{
get { return str[0]; }//此处代码没有意义仅只是测试
set { str[0] = value; }
}
}
2、静态属性
C#支持静态无参属性
C#不支持静态有参属性(索引器),C#对于索引器的语法是this[],这种语法非常的友好,this代表当前进行索引的具体对象,也正是由于这个原因,因为索引器只属于实例对象,所以,索引器是不允许有静态属性。
3、虚属性
既然属性的本质是一组方法,那么自然就可以实现多态的机制,由于virtual修饰符只能作用于整个属性,因此,如果要实现虚属性,对于生成托管集中的两个方法则都为虚方法
例:
public class Person
{
private string name;
private int age;
public virtual string Name
{
get
{
Console.WriteLine("Person getor");
return name;
}
set
{
Console.WriteLine("Person setor");
name = value;
}
}
public Int32 Age
{
get { return age; }
set
{
if (value < 0)
{
throw new ArgumentOutOfRangeException("the value must be greater than 0");
}
age = value;
}
}
public Person()
{
}
public Person(string n, int a)
{
name = n;
age = a;
}
}
public class Coder : Person
{
private string c_name;
public Coder(string name)
: base(name, 24)
{
c_name = name;
}
public override string Name
{
get
{
Console.WriteLine("Coder getor");
return c_name;
}
set
{
Console.WriteLine("Coder setor");
c_name = "Coder:" + value;
}
}
}
Person p = new Person("LPF", 24);
Console.WriteLine(p.Name);//get
p.Name = "NEWLPF";//set
Console.WriteLine(p.Name);
Person c = new Coder("LPF");
Console.WriteLine(c.Name);//get
c.Name = "NEWLPF";//set
Console.WriteLine(c.Name);
Console.Read();
输出:
从上面的输出结果来看,显然属性是支持虚的,根据属性的本质是方法也可以确定,在我们给Name属性添加了virtual后,我们可以反编译查看一下,Name属性生成的set_Name和get_Name方法:
对于属性的多态的实现与虚方法的实现是一样的道理,在此便不再分析
注意:对于虚属性,virtual、override只能作用于整个属性,不能单独作用于get和set
4、属性的访问性
属性或索引器的get和set部分称为“访问器”。 默认情况下,这些访问器具有相同的可见性或访问级别:其所属属性或索引器的可见性或访问级别。
不过,有时限制对其中某个访问器的访问会很有用。通常是在保持get访问器可公开访问的情况下,限制set访问器的可访问性
例如:
private string name = "Hello";
public string Name
{
get
{
return name;
}
protected set
{
name = value;
}
}
属性访问修饰符的限制:
1、不能对接口或显示接口成员实现使用访问器修饰符
2、仅当属性或索引器同时具有set和get访问器时,才能使用访问修饰符,这种情况下只允许对其中一个访问器使用修饰符
3、如果属性或索引器具有override修饰符,则访问器修饰符必须怀重写的访问器的访问器匹配
4、访问器的可访问性级别必须比属性或索引器本身的可访问性级别具有更严格的限制