用类型编程
这一章的主要内容是介绍用类型可以做什么,以及一些函数的使用范例。我认为有用的部分主要有三个:类型的内存分布结构、类型函数的使用、元数据扩展(利用属性)
1、引用、对象、类型在内存中如何分布的?
这部分解决了我这方面的很多疑惑,直接上图:
使用对象时,我们通过“引用”来使用对象,所谓“引用”不过也是托管指针罢了(托管指针是指向托管内存的一个指针),途中的Reference即是。引用指向的对象在内存中怎么存储的呢?首先有一个sync#,这个是用来记录资源信息的,锁之类,这个我还没有涉及,不多说。重要的是后面有一个htype的类型指针,这个指针指向一块不透明(ms没有文档公布)的数据结构,这个结构里记录了类型的的基本信息,所以同一Type的实例的htype段存着同一个值,那就是指向这个Type结构的指针。最后就是对象的field了。这个图解决了我关于对象如何找到类的问题。
那么,类结构里又怎么存的呢?下图:
类型的结构中有两个重要内容——接口表和base指针。接口表是说这个类型如果实现了任何接口,都会在这个表里有个entry, 每个entry都有一个指向接口结构的指针。base指针则是指向这个类型的直接父类的内存结构。
这样我们就知道CLR是如何判断一个类是否实现(继承)了某个接口(类),只需要在借口表里顺序查找(或是根据base指针逐级查找即可)。可以看出,类型的向上转换的开销来源于此。至于强制转换,则有另外的开销,这里不述。
PS:在c#中,类型转换用as、is。不同之处在于,as将实例直接转换为目标类型返回,is则是返回bool值。如果的确需要转换,那么用as比较好,用is的话,判断时强制转换一次,真正转换又一次,相当于重复执行了一遍。
2、虽然Type的内存结构没有明确的文档定义,但是,运行库提供了编程方法来取得这些信息,这就是System.Reflection。
利用Reflection(反射机制),我们可以取得想要的一切Type信息。下面选几个比较有用的。
获取Type的方法:
a. System.Object.GetType() 取得值或对象的类型,返回System.Type类型。
b. typeof 运算符,传入类型字符串,返回System.Type,例如,typeof(Int32)。
c. Assembly.GetType() 取得程序集中的类型,需要传入字符串。例如:assm.GetType(“System.Type”)
Type类包含的有用方法:
a. 类型兼容性测试方法:
IsSubclassOf(Type t), 当前类是t的子类型,返回true。
IsAssignableFrom(Type t),当前类与t类型相同 | 当前类是t父类 | 当前类是t实现的借口,则返回false。
IsAssignableFrom()用的多,因为可判断情况多。
b. Type.GetInterfaces()获取Type实现的接口。
c. Type可以得到的信息很多,如下图所示:
类的层级结构图如下:
因为类成员有访问限制,所以在调用GetMembers()等方法时可以传入BindingFlags参数,用以指定需要的成员。
3、三个特殊方法
a. getter和setter
这个不陌生,有这两个方法的字段叫做“property“,其实,类型中的property编译时,会分别产生get和set函数。如下所示:
产生的中间代码如下:
编译器自动添加了get_Price和set_Price两个函数。Price的中间码如下:
.property instance int32 Price()
{
.get instance int32 LinaTest.Vinegar::get_Price()
.set instance void LinaTest.Vinegar::set_Price(int32)
} // end of property Vinegar::Price
当调用Price property时,调用的实际是这两个函数。
b. 事件 event
对于类型中的event,编译器会自动生成add方法和remove方法。例如:
中间码为:
OnSubmit的中间码为:
.event [mscorlib]System.EventHandler OnSubmit
{
.removeon instance void LinaTest.Vinegar::remove_OnSubmit(class [mscorlib]System.EventHandler)
.addon instance void LinaTest.Vinegar::add_OnSubmit(class [mscorlib]System.EventHandler)
} // end of event Vinegar::OnSubmit
和property道理一样的。
c. indexer
和上面的property、event同理,indexer也是将对成员的访问重定向到编译器自动生成的方法。代码如下:
中间代码为:
.property instance int32 Item(int32)
{
.get instance int32 LinaTest.Vinegar::get_Item(int32)
} // end of property Vinegar::Item
注意这里写了两个indexer,一个用string索引,一个用int。
4、元数据扩展
经常能看到在class、method、field前面有用中括号括起来的一些东西,这就是属性。
属性也是一个类型,是继承于System.Attribute类的,c#、c++里用中括号来使用属性。属性的作用就是将一些信息写入到元数据中,以便让编译器或者其他程序读取。由于Attribute也是有构造函数的(不然怎么让CLR初始化),使用格式就是如下:(这是自定一个属性,当然库里也有其他属性可用)
这时元数据如下:
在代码中如何取出属性值呢?
使用属性时,不只可以通过构造函数的形式,也可以用名字传递,不过需要属性类中的field是public的,而且,不能写带这个参数的构造函数了。根据这个修改上面的代码,如下:
关于属性的使用一个例子:msdn上举的,拿过来用啦~
这段代码执行时,编译器会读取Obsolete属性,所以在编译过程中会有Obsolete的警告。