属性分为无参属性和有参属性(即索引器)。
属性相对于字段的优点不仅仅是为了封装,还可以在读写的时候做一些额外操作,缓存某些值或者推迟创建一些内部对象,也适用于以线程安全的方式访问字段。
话说最基本的属性就不讲了,太平常了。
基本上很多文章都是讲属性的好处的,所以下面就讲一下属性的不足:
属性不能作为out和ref传参。属性实质上是方法而不是字段,因为属性在编译后实际上是方法。
属性可能花较长时执行,而字段访问则是立即完成。
许多人使用属性是为了线程同步,这就可能造成线程永远终止,要线程同步就不要使用属性。
如果属性所在类可以被远程访问,那么调用属性会非常慢。
在上面这些情况下,应该优先使用方法而不是属性。
并且《CLR via C#》的作者十分不推荐使用属性,而是使用方法。
匿名类型
先上一段匿名类型的代码
var man=new {Name="Troy",Age=1};
这段代码在C#编译器中会先推断Name和Age的类型,然后给他们分别创建私有字段,再然后就为这两个私有字段创建只读属性。然后就创建一个构造器初始化私有只读字段。
并重写Object的Equals(判断每个字段是否都想等),GetHashCode(根据每个字段的哈希码生成哈希码)和ToString(返回“属性=值”对的以逗号分隔的列表)。
如果编译器发现代码中定义了多个匿名类型,但是这些匿名类型的每个属性都具有相同的类型和名称以及顺序。那么实际上它只会创建一个匿名类型定义。
Tuple类型
在System命名空间中,有几个泛型Tuple(元组)类型,它们全部从Object派生,区别只在于元数的个数。
和匿名对象一样,Tuple创建好后就不可变了(所有属性都是只读)。
匿名对象的属性调用是通过具体的属性名,然而Tuple类型只能通过Item1这样的方式调用。例子如下:
var man = new Tuple<string, int, string>("Troy", 1, "男");//构造器方式 var boy = Tuple.Create("Fuckboy", 3, "男");//静态工厂方法创造 Console.WriteLine(man.Item1);//Troy Console.WriteLine(man.Item2);//1 Console.WriteLine(man.Item3);//男 //匿名对象方式 var myObj = new { Name = "Troy", Age = 1, Sex = "男" }; Console.WriteLine(myObj.Name);//Troy Console.WriteLine(myObj.Age);//1 Console.WriteLine(myObj.Sex);//男 //另一种组合键值对的方式,感觉像在写js dynamic obj = new System.Dynamic.ExpandoObject(); obj.Name = "Troy"; obj.Age = 1; obj.Sex = "男"; Console.WriteLine(obj.Name);//Troy Console.WriteLine(obj.Age);//1 Console.WriteLine(obj.Sex);//男 Console.Read();
有参属性(索引器)
C#使用数组风格的语法来公开有参属性。
CLR本身并不区分有参属性和无参属性,对它而言,每个属性都只是类型中定义的一对方法和一些元数据。而用数组风格的语法来玩有参属性是C#特有的,编译之后还是会编译成IL代码中的方法。
JIT对属性的优化
对简单的get和set访问其方法,JIT编译后会将访问其代码直接嵌入到调用属性的方法中,这样就避免了运行时发出调用所产生的开销。代价就是编译后的方法变得更大,可以想象一下调用属性的方法有很多个,那么嵌入的重复代码就有很多。
所以我们应该在属性访问器里写的代码应该尽可能少,否则用方法也不错。如果这样做了,那么JIT这样优化后,生成的本机代码会变得更小,而且执行更快。