类型属性(Property)解析
分类索引:C# 语言和运行时剖析--前言
属性的定义
一.概念
- 特性:在语义上接近于字段(Field),但在使用上接近于方法的一种类成员。
- 使用场景:最常用于简单封装和检查类/对象的数据成员(Field)。 属性也可以被称为智能字段。
- 属性的用法:无参属性(默认), 有参属性(索引器).
- 注意:属性的最重要作用是封装字段,使字段不暴露在类型外部随意被随意修改。也可以使用方法来达到这一目的,但属性更为轻量级。
二.代码示例
internal class Employee { private string name; private Int32 age; public Int32 Age { get { return this.age; } set { if (value < 0) throw new ArgumentOutOfRangeException("value", value.ToString(), "年龄必须大于等于0"); this.age=value; } } public string Name { get { return this.name; } set { this.name=value; } } }
三.在对象初始化器中使用属性
-
说明:我们经常在初始化对象时,初始化对象的一些公共属性,可以使用简化的方法在对象构造器中直接初始化属性。
- 优点:
- 简化初始化语法。
- 使代码表达式的上下文不中断,可以连接多个方法,增强了代码的可读性和优美成都。
- 代码示例:
string s = new Employee() { Age = 20, Name = "Simon" }.ToString().ToUpper(); //相当于如下操作: Employee employee = new Employee(); employee.Age = 20; employee.Name = "Simon"; string s1 = employee.ToString(); s1 = s1.ToUpper();
前一种表达式的表达能力显然更强。
- 在集合初始化时,也可以使用类似语法,如下:
internal class Employee { private string name; private Int32 age; private List<string> jobs; public List<string> Jobs { get { return this.jobs; } set { this.jobs=value; } } public Int32 Age { get { return this.age; } set { if (value < 0) throw new ArgumentOutOfRangeException("value", value.ToString(), "年龄必须大于等于0"); this.age=value; } } public string Name { get { return this.name; } set { this.name=value; } } } class Program { static void Main(string[] args) { Employee employee = new Employee() { Age = 20, Name = "Simon", Jobs = { "Specialist","Supervisor","Manager"} }; } }
编译器将会自动将Jobs属性中字符串匹配到List的Item中。
自动实现属性
一.概念
- 使用场景:如果只是为了封装一个支持字段而创建一个属性,不需要实现任何逻辑的情况下,C#提供一种更简单的语法,称为自动实现属性。
- 原理:此种情况下,C#会自动为你声明一个私有字段。注意,字段不是被省略了,只是不用显示的定义而已。
- 示例:如下类型Employee中的Name属性就是一个自动实现属性。
internal class Employee { private string Name {get;set;} private Int32 age; private List<string> jobs; public List<string> Jobs { get { return this.jobs; } set { this.jobs=value; } } public Int32 Age { get { return this.age; } set { if (value < 0) throw new ArgumentOutOfRangeException("value", value.ToString(), "年龄必须大于等于0"); this.age=value; } } }
二.不适用的场景
- 由编译器自动生成的字段往往名称不固定,每次重新编译时,这个字段的名称都有可能改变。所以,具有自动实现属性的类型无法被序列化和反序列化。
- 自动实现属性,由于不具备任何逻辑,所以无法被调试。
- 虽然自动实现属性比较简单实现,但一般还是不推荐使用这个功能。
有参属性(索引器)
一.概念
- 使用场景:当类型具有集合的特性时,可以使用有参属性。
- 使用方法:get访问器方法可以接受一个或者多个参数,set访问器方法接受两个或者多个参数。
- 优点:如果说属性是一种智能字段,那么有参属性就是一种智能数组。从实践来讲,有参属性并没有提供什么新功能,也是对代码表达能力的一种提升。
- 示例:C#使用数组风格的语法来公开有参属性。
public sealed class BitArray { //因为C#中没有直接的Bit数据类型,所以使用Byte数组保存Bit private Byte[] byteArray; private Int32 numBits; public BitArray(Int32 numBits) { if (numBits <= 0) throw new ArgumentOutOfRangeException("numBits 必须大于0"); this.numBits = numBits; //保证小于8位的bit能被分配到Byte中 byteArray = new Byte[(numBits) + 7 / 8]; } //索引器定义 public Boolean this[Int32 bitPos] { get { if ((bitPos < 0) || (bitPos >= numBits)) throw new ArgumentOutOfRangeException("bitPos"); return (byteArray[bitPos / 8] & (1 << (bitPos % 8))) != 0; } set { if ((bitPos < 0) || (bitPos >= numBits)) throw new ArgumentOutOfRangeException("bitPos"); if (value) { byteArray[bitPos / 8] = (Byte)(byteArray[bitPos / 8] | 1 << (bitPos % 8)); } else { byteArray[bitPos / 8] = (Byte)(byteArray[bitPos / 8] & ~1 << (bitPos % 8)); } } } } class Program { //调用索引器的get和set演示 static void Main(string[] args) { BitArray ba = new BitArray(14); for (Int32 x = 0; x < 14; x++) { ba[x] = (x % 2 == 0); } for (Int32 x = 0; x < 14; x++) { Console.WriteLine("Bit" + x + " is " + (ba[x] ? "On" : "Off")); } } }
二.实践注意
- C#只支持对象的索引器,不支持静态的索引器。
- C#可以重载索引器,但是重载的索引器必须使用不同类型的形参。
属性使用最佳实践讨论
一.属性具有方法特性
- 相同点:
- 属性可以抛出异常
- 可以使用虚属性或者抽象属性(对应虚方法和抽象方法)
- 可以在接口中使用属性,使实现接口的类型必须实现属性
- 不同点:
- 属性只能使用get/set语句
- 属性不应该处理较为复杂的运算逻辑,不利于数据字段的线程同步。
二.属性在语义上接近字段
- 属性的主要应用场景应该是对类型字段和数据状态的封装,检查,简单计算等,不应该滥用于其他场景。