CLR via C# 方法 实例构造器和类(引用类型)
//--
构造器是将类型的实例初始化为良好状态的特殊方法。构造器方法在“方法定义元数据表”中始终叫做.ctor(constructor的简称)。创建引用类型的实例时,首先为实例的数据字段分配内存,然后初始化对象的附加字段(类型对象指针和同步块索引),最后调用类型的实例构造器来设置对象的初始化状态。
//--
构造引用类型的对象时,在调用类型的实例构造器之前,为对象分配的内存总是先被归零。没有被构造器显式重写的所有字段都保证获得0或者null值。
//--
和其他方法不同,实例构造器永远不能被继承。也就是说,类只有类自己定义的实例构造器。由于永远不能继承实例构造器,所以实例构造器不能使用以下修饰符:virtual,new,override,sealed和abstract。如果类没有显式定义任何构造器,C#编译器将定义一个默认(无参)构造器。在它的实现中,只是简单地调用了基类的无参构造器。
下面这两种定义是等价的:
//--
如果类的修饰符为abstract,那么编译器生成的默认构造器的可访问性就为protected;否则,构造器会被赋予public可访问性。如果基类没有提供无参构造器,那么派生类必须显式调用一个基类构造器,否则编译器会报错。如果类的修饰符为static(sealed和abstract静态类在元数据中是抽象密封类),编译器根本不会在类的定义中生成默认构造器。
//--
一个类型可以定义多个实例构造器。每个构造器都必须有不同的签名,而且每个都可以有不同的可访问性。为了使代码“可验证”(verifiable),类的实例构造器在访问从基类继承的任何字段之前,必须先调用基类的构造器。如果派生类的构造器没有显式调用一个基类构造器,C#编译器会自动生成对默认的基类构造器的调用。最终,System.Object的公共无参构造器会得到调用。该构造器什么都不做,会直接返回。由于System.Object没有定义实例数据字段,所以它的构造器无事可做。
//--
极少数时候可以在不调用实例构造器的前提下创建类型的实例。一个典型的例子就是Object的MemberwiseClone方法。该方法的作用是分配内存,初始化对象的附加字段(类型对象指针和同步块索引),然后将源对象的字节数据复制到新对象中。另外,用运行时序列化器(runtime serializer)反序列化对象时,通常也不需要调用构造器。反序列化代码使用System.Runtime.Serialization.FormatterServices类型的GetUninitializedObject或者GetSafeUninitializedObject方法为对象分配内存,期间不会调用一个构造器。
//--
重要提示:不要在构造器中调用虚方法。原因是假如被实例化的类型重写了虚方法,就会执行派生类型对虚方法的实现,但在这个时候,尚未完成对继承层次结构的所有字段的初始化(被实例化的类型的构造器还没有运行呢)。所以,调用虚方法会导致无法预测的行为。归根到底,这是由于调用虚方法,直到运行时之前都不会选择执行该方法的实际类型。
//--
它的IL代码如下:
(可以搜索一下这个ildasm怎么用哈,还是自己写一写)
可以看出,SomeType的构造器把值5存储到字段m_x,再调用基类的构造器。换句话说,C#编译器提供了一个简化的语法,允许以“内联”(其实就是嵌入)方式初始化实例字段。但在幕后,它会将这种语法转化成构造器方法中的代码来执行初始化。
//--
这同时提醒我们注意代码的膨胀效应。如下类定义:
编译器为这三个构造器方法生成代码时,在每个方法的开始位置,都会包含用于初始化m_x,m_s,m_d的代码。在这些初始化代码之后,编译器会插入对基类构造器的调用。再然后,会插入构造器自己的代码。
即使没有代码显式初始化m_b,m_b也保证会初始化为0。
//--
编译器在调用基类构造器前使用简化语法对所有字段进行初始化,以维持源代码给人留下的“这些字段总是有一个值”的印象。但假如基类构造器调用了虚方法并回调由派生类定义的方法,就可能出现问题。在这种情况下,使用简化语法初始化的字段在调用虚方法之前就初始化好了。
//--
由于有三个构造器,所以编译器生成三次初始化m_x,m_s,m_d的代码-----每个构造器一次。如果有几个已初始化的实例字段和许多重载的构造器方法,可以考虑不是在定义字段时初始化,而是创建单个构造器来执行这些公共的初始化。然后,让其他构造器都显式调用这个公共的初始化构造器。这样能减少生成的代码。
internal sealed class SomeType
{
private int m_x;
private string m_s;
private double m_d;
private byte m_b;
public SomeType()
{
m_x = 5;
m_s = "hi there";
m_d = 3.14159;
m_b = 0xff;
}
public SomeType(int x) : this()
{
m_x = x;
}
public SomeType(int x, string s) : this()
{
m_x = x;
m_s = s;
}
}
IL