第十章 属性
CLR提供了两种属性:无参属性和含参属性。前者通常被称为属性(property),后者C#称为索引器。
Ø 10.1无参属性
将所有字段的访问限制都设为私有方式或者至少是保护方式——永远不要设为共有方式,然后再以方法的形式让用户读取或者设置对象的状态信息。封装了对字段访问的方法典型地被称为访问器方法(accessor method)。访问器方法可以选择执行任何的数据合理性检查来确保对象的状态不被破坏。
访问器方法的缺点:
1. 需要编写更多的代码,因为必须要实现额外的方法
2. 类型的用户必须调用方法而不是简单地引用字段名称
CLR支持静态属性,实例属性和虚属性。另外属性可以标记任何的访问修饰符,也可以定义在接口中。
每个属性都有一个名称和一个类型,属性不能被重载。
定义属性会在生成的托管模块中产生以下三项:
1. 一个表示属性的get访问器的方法。只有在为属性定义了get访问器方法时,才有这一项。
2. 一个表示属性的set访问器的方法。只有在为属性定义了set访问器方法时,才有这一项。
3. 一个位于托管模块元数据中的属性的定义,不管是只读,只写,或者读写属性都有这一项。
可以通过System.Reflection.PropertyInfo类来获取属性。
程序运行时,属性的底层处理:
对于简单的get和set访问器方法,JIT编译器会将代码进行内联处理,这样使用属性时就不会再有运行时的性能损失。内联会将一个方法(或者访问器方法)内的代码直接编译到调用它们的方法中,从而消除了调用方法时的运行时负担,但它的代价是编译后的方法代码会变得较为庞大。由于属性的访问器方法通常包含有很少的代码,所以内联它们会使代码变得更小,执行效率也更高。
Ø 10.2含参属性
索引器必须至少有一个参数,也可以有多个。这些参数(以及返回值)可以是任意类型。
与无参属性的set访问器类似,索引器的set访问器方法也有一个隐含的参数(C#称之为value),该参数表示“被索引元素”期望的新值。
定义含参属性也会在生成的托管模块中产生以下三项:
1. 一个表示含参属性的get访问器的方法。只有在为含参属性定义了get访问器方法时,才有这一项。
2. 一个表示含参属性的set访问器的方法。只有在为含参属性定义了set访问器方法时,才有这一项。
3. 一个位于托管模块元数据中的属性的定义,不管是只读,只写或者读写属性都有这一项。不存在特别的含参属性数据定义表,因为对CLR来讲,含参属性就是属性。
一个类型可以定义多个索引器,前提是这些索引器的参数集合各不相同。
C#将索引器看作是重载[]操作符的一种方式,而该操作符不能用来辨析具有不同方法名称的含参属性。
对于CLR来讲,无参属性和含参属性之间没有任何区别,所以我们同样可以使用System.Reflection.PropertyInfo类来查找一个含参属性与它的访问器方法之间的关联关系。
Ø 10.1.1 自动实现的属性
如果只是为了封装一个支持字段而创建一个属性,C#还提供了一种更简单的方法,称为自动实现的属性(AutomaticallyImplemented Property, AIP).
运行时序列化引擎将字段名持久存储到序列化的流中。AIP的支持字段的名称是由编译器决定的,而且每次重新编译代码,它都可能更改这个名称。这样一来,任何类型只要包含一个AIP,便没办法对该类型的实例进行反序列化了。在任何想要序列化或反序列化的类型中,都不要使用AIP功能。
如果使用AIP,属性必然是可读和可写的。AIP是作用于整个属性的;要么都用,要么都不用。
属性不能作为out或ref参数传给方法;字段却可以。
Ø 对象和集合初始化器
如果属性的类型实现了IEnumerable或IEnumerable<T>,但未提供Add方法,编译器就不允许使用集合初始化语法向集合中添加数据项。
using System.Collections.Generic;
namespace ConsoleApp2012_1
{
class Program
{
static void Main(string[] args)
{
ClassRoom classRoom_1 = new ClassRoom()
{
Students = { "Tom", "Jimmy", "Aiden" },
Num = { { "Tom", 1 }, { "Jimmy", 2 }, { "Aiden", 3 } }
};
ClassRoom classRoom_2 = new ClassRoom
{
Students = { "Tom", "Jimmy", "Aiden" },
Num = { { "Tom", 1 }, { "Jimmy", 2 }, { "Aiden", 3 } }
};
}
}
class ClassRoom
{
private List<string> m_Students = new List<string>();
public List<string> Students { get { return this.m_Students; } }
private Dictionary<string, int> m_Num = new Dictionary<string, int>();
public Dictionary<string, int> Num { get { return this.m_Num; } }
public ClassRoom() { }
}
}
Ø 10.1.4 匿名类型
using System;
using System.IO;
using System.Linq;
namespace ConsoleApp2012_1
{
class Program
{
static void Main(string[] args)
{
var o1 = new { Name = "Jeff", Year = "1946" };
var peoples = new[]
{
o1,
new { Name = "Jeffery", Year = "1946" },
new { Name = "Anders", Year = "1946" },
new { Name = "Bill", Year = "1946" }
};
foreach (var people in peoples)
Console.WriteLine("Name:{0} Year:{1}", people.Name, people.Year);
Console.WriteLine();
string myDocuments = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
var query =
from pathname in Directory.GetFiles(myDocuments)
let lastWriteTime = File.GetLastWriteTime(pathname)
where lastWriteTime > (DateTime.Now - TimeSpan.FromDays(7))
orderby lastWriteTime
select new { Path = pathname, lastWriteTime };
foreach (var file in query)
Console.WriteLine("Path:{0} {1}lastWriteTime{2}", file.Path, Environment.NewLine, file.lastWriteTime);
Console.ReadKey();
}
}
}
匿名类型经常与LINQ(Language Intergrated Query,语言集成查询)技术配合使用。可用LINQ执行查询,从而生成由一组对象构成的集合,这些对象都是相同的匿名类型。然后可以对结果集中的对象进行处理。
匿名类型的实例不能泄露到一个方法的外部。在方法原型中,无法要求它接受一个匿名类型的参数,因为没有办法指定匿名类型。类似地,方法也无法指定它要返回对一个匿名类型的引用。虽然可以将匿名类型的实例视为一个Object,但没办法将Object类型的变量转型回一个匿名类型,因为不知道匿名类型在编译时的名称。
Ø 10.1.5 System.Tuple类型
主要用于当一个函数需要返回多个值时
using System;
public Tuple<string, string> GetFileInfo(string filePath)
{
return Tuple.Create<string, string>(File.GetCreationTime(filePath).ToString(), File.GetLastWriteTime(filePath).ToString());
}