《CLR via C#》精髓:构造器

构造器包括实例构造器(Instance Constructor)和类型构造器(Type
Constructor)。它们针对引用类型和值类型拥有不同的行为和准则。

实例构造器


引用类型


  1. 实例构造器用于初始化对象状态;

  2. 被调用前,对象所分配的内存总会被清零;

  3. 永远不会被继承;

  4. 如果没有显式地定义任何构造器,C#编译器会自动生成一个默认(无参)构造器,其实现代码将调用基类的无参构造器;

  5. 对于抽象类(abstract),编译器生成的默认构造器的可访问性为protected;

  6. 对于非抽象类,编译器生成的默认构造器的可访问性为public;

  7. 如果基类并没有提供无参构造器,派生类必须显式地调用基类的构造器,否则将出现编译错误;

  8. 对于静态类,编译器不会生成任何默认构造器;

  9. 如果派生类的构造器没有显式地调用基类的任何一个构造器,C#编译器将自动调用基类的默认构造器;

  10. 不要在构造器中调用任何虚方法,这将导致不可预知的行为;

  11. 如果类型定义字段时使用了内联初始化语法,编译器在调用基类构造器之前,会首先初始化所有使用了内联初始化语法的字段;

  12. 如果类型中有多个字段以及多个构造器,应避免对字段使用内联初始化语法,而是创建一个构造器完成字段的基本初始化,并让其它构造器显式地调用这个基本初始化构造器,完成特定字段的最终初始化。

值类型


  1. CLR总是允许创建值类型实例,并且无法阻止值类型被初始化。鉴于此,C#编译器不会为值类型生成默认无参构造器;

  2. 出于性能方面的考虑,对于包含在引用类型中的值类型字段,CLR不会调用它们的构造器;

  3. CLR确实允许为值类型定义构造器,但这些构造器只有被显式地调用时才会被执行;

  4. C#不允许为值类型定义无参构造器(但CLR允许),因此也就不允许值类型实例字段的内联初始化;

  5. 值类型中的任何构造器都必须完成值类型全部字段的初始化。

类型构造器


  1. 类型构造器可被应用于接口(C#不允许)、引用类型和值类型;

  2. 类型默认情况下没有类型构造器;

  3. 一个类型只能拥有一个类型构造器,且不能包含参数;

  4. 类型构造器总是private(C#会自动添加);

  5. 虽然可以为值类型定义类型构造器,但不要真的这么做,因为C#并不保证一定会调用它(原因见后文类型构造器何时被调用部分);

  6. 类型构造器初始化任何单例对象(Singleton)的好地方;

  7. 由于类型构造器由CLR调用(何时调用以及谁先谁后都由CRL决定),你应该永远避免编写依赖类型构造器按特定顺序调用的代码;

  8. 类型构造器中的代码只用来访问类型的静态字段,这也正是类型构造器的通常用途;

  9. 虽然C#不允许对值类型的实例字段进行内联初始化(原因见上文实例构造器——值类型部分的第4条),但允许对静态字段进行内联初始化;

  10. 如果对类型中静态字段进行了内联初始化,类型中也未定义类型构造器,C#编译器将自动为类型生成类型构造器,并在构造器内部为拥有内联初始化行为的静态字段生成初始化代码;

  11. 如果对类型中静态字段进行了内联初始化,并定义了类型构造器,C#编译器为类型构造器所生成的IL代码中将首先包括拥有内联初始化行为静态字段的初始化代码,然后才是类型构造器中显式定义的代码;

  12. 不要在类型构造器中调用基类的类型构造器,这种调用没有意义,因为类型的静态字段不会从基类继承或共享。

类型构造器何时被调用?


  • 对于引用类型,当类型的静态成员被访问,或者类型的实例被创建时,类型构造器将被调用;

  • 对于值类型,当类型的静态成员被访问,或者类型的一个实例构造器被调用时,类型构造器将被调用。

由此可以得出如下推论:

  1. 如果仅定义了引用类型变量但未实例化,那么类型构造器将不会被调用;

  2. 如果值类型实例被创建时没有调用实例构造器,那么类型构造器将不会被调用;

  3. C#保证:引用类型和值类型的任何静态成员被访问前的某个时刻,类型构造器会被调用。

下例代码是针对上述结论和推论的验证:


using System;

// 值类型
internal struct SomeValType {
// 类型构造器
static SomeValType() {
Console.WriteLine("This never gets displayed");
}

// 实例构造器
public SomeValType(Int32 x) {
m_x = x;
}

public Int32 m_x;
}

// 引用类型
internal class SomeRefType {
// 类型构造器
static SomeRefType() {
Console.WriteLine("This will be display");
}

public Int32 m_x;
}

public sealed class Program {
public static void Main() {
SomeValType valType = new SomeValType();
valType.m_x = 123;

SomeRefType refType = new SomeRefType();
refType.m_x = 456;
}
}

上述代码执行后输出结果为:

This will be display

很明显,引用类型(SomeRefType)的类型构造器被调用了,因为引用类型的实例被创建了;而值类型(SomeValType)的类型构造器没有被CLR调用,因为值类型的实例构造器没有被调用。正像类型构造器部分第5条所说的那样:“C#并不保证一定会调用它”,这也正符合推论2。

值类型的实例构造器没有被调用的原因就在于Main方法中值类型的实例化代码:

SomeValType valType = new SomeValType();

我们知道,值类型不允许定义默认无参实例构造器,上述代码只是定义一个SomeValType值类型实例并将其全部实例字段初始化为0或null,不会调用任何实例构造器。

如果我们确实希望值类型的类型构造器被调用,那么只需将上述代码修改为:

SomeValType valType = new SomeValType(789);

修改后的代码显式地调用了SomeValType值类型的一个实例构造器,输出结果如下:

This never gets displayed
This will be display

显然,值类型的类型构造器被调用了,因为现在已经符合“对于值类型,当类型的静态成员被访问,或者类型的一个实例构造器被调用时,类型构造器将被调用。”一句中“类型的一个实例构造器被调用”的要求。

上述试验其实又从反方向证明了类型构造器部分的第5条:“C#并不保证一定会调用它”——C#不保证一定会,但也不保证一定不会。

时间: 2024-08-29 12:49:48

《CLR via C#》精髓:构造器的相关文章

CLR静态构造器

CLR保证一个类型构造器在每个AppDomain中只执行一次,而且这种执行是线程安全的. 作用: 就是初始化静态成员 比如有几个静态成员需要初始化那你把初始化代码放到哪呢? 放到普通构造函数里,那肯定不行.因为静态成员没有创建实例就要可用. 专门建一个static public方法来初始化?这样用起来非常不方便,你需要在“第一次”使用静态成员前先调用这个方法.如果你在使用静态成员前忘了调用该方法,会导致错误.如果重复调用,又是冗繁操作. 所以静态构造函数就派上用场了.它会在你第一次调用静态成员(

CLR via C#深解笔记四 - 方法、参数、属性

实例构造器和类(引用类型) 构造器(constructor)是允许将类型的实例初始化为良好状态的一种特殊方法.构造器方法在“方法定义元数据表”中始终叫.ctor. 创建一个引用类型的实例时: #1, 首先为实例的数据字段分配内存 #2, 然后初始化对象的附加字段(类型对象指针和同步块索引) #3, 最后调用类型的实例构造器来设置对象的初始状态 构造引用类型的对象时,在调用类型的实例构造器之前,为对象分配的内存总是先被归零.构造器没有显示重写的所有字段保证都有一个0或null值.和其它方法不同,实

实例构造器和类型构造器

实例构造器,新建对象时,设置对象的初始状态,平时接触的比较多,就不多说了. 类型构造器(静态构造器,类构造器),用来设置类型的初始状态.类型默认没有定义类型构造器,如果定义,也只能定义一个.此外,类型构造器没有参数,代码示例如下: internal sealed class SomeRefType { static SomeRefType() { // SomeRefType被首次访问时,执行这里的代码 } } 类型构造器的定义类似于无参的实例构造器,区别在于必须将其标记为static.此外,类

NET经典书籍必读

C#与.NET框架,入门 + 进阶 + 精通,外加并发编程实例,10本C#图书,一本都不能少. 1.<Learning hard C#学习笔记> 作者:李志  书号:978-7-115-38292-4  定价:49.00元  开本:16  页数:304 博客园超人气博主.微软MVP.资深.NET软件开发工程师Learning hard告诉你怎么学习C#!一书在手,C#不愁! “本书是作者亲身学习经历的真实总结,很适合C#和.NET初学者.Web开发工程师以及计算机专业的学生阅读.作者从不爱学习

.Net Framework 与 .Net Core中的静态构造函数

起因 前几天在看书的时候看到一条内容 > 由于CLR保证一个类型构造器在每个AppDomain中只执行一次,而且(这种执行)是线程安全的,所以非常适合在类型构造器中初始化类型需要的任何单实例(Singleton)对象. 忽然想到,平时自己使用过程中都是通过Lazy来延迟化构建单例对象,就产生了一个疑问,使用Lazy跟直接使用静态构造函数有什么区别,有什么优缺点呢? 经过 抱着这个疑问,我查了一下相关的内容,发现一个很有意思的东西. 关于静态字段的初始化有两种写法 internal sealed

CLR类型设计之方法与构造器

C#语言中最常用到的就是方法的语法,如果在控制台应用程序中,一定要有一个Main()方法作为程序入口,本文讨论的不是方法实现也不是为什么要写方法,而是来讨论下如何构造器和扩展方法. 无论学习那门语言都要学习函数体,C#,JAVA,PHP,都会涉及到函数体,而C#的函数体成员并不少,方法和构造器就是函数体成员之一,函数体成员还包括但不限于:方法,属性,构造器,终结器,运算符及索引器. 方法就是某个类相关的函数,也可以返回简单的基元类型或者什么也不反回,方法可以定义其公开性,如果使用static修饰

《CLR via C#》精髓:C#是个“骗子”!

其实,CLR非常"单纯",他远没有C#(确切说应该是C#编译器,下同)那样"狡猾"!多年以来,C#一直对CLR隐瞒着许多事情.这不仅欺骗了纯真的CLR,更迷惑了数以万计天真的开发者,让他们误以为很多事都是CLR干的. 随着C#年龄的增长(版本升级),他的这种欺骗行为也愈演愈烈.CLR越来越无助,开发者越来越迷茫.是真相大白于天下的时候了,是细数C#"罪状"的时候了. 1.CLR根本不知道"命名空间"(Namespace)这回事

《CLR via C#》精髓:既生dynamic何生var?

对于使用var关键字声明局部变量,编译器将根据表达式来推断变量的最终的数据类型: 通过dynamic表达式或变量调用成员(字段.属性.方法委托等)时,编译器将生成特殊IL代码,这些代码被称为Payload代码.在运行时,Payload代码将根据dynamic表达式或变量所引用对象的确切类型来决定最终要执行的操作: var关键字只能用于声明方法内的局部变量,而dynamic关键字则可以用于局部变量.字段和参数: 你无法将一个表达式转型为var,但你可以将一个表达式转型为dynamic: 你必须显式

《CLR via C#》之线程处理——任务调度器

<CLR via C#>之线程基础--任务调度器 <CLR via C#>之线程基础--任务调度器线程池任务调度器设置线程池限制如何管理工作者线程同步上下文任务调度器自定义TaskScheduler派生类 FCL提供了两个派生子TaskScheduler的类型:线程池任务调度器(thread pool task scheduler),和同步上下文任务调度器(synchronization context task scheduler).默认情况下都使用线程池任务调度器. 线程池任务