C#类型构造器

简介:

类型构造器也称为静态构造器,类构造器,或类型初始化器

类型构造器可以用于接口(C#不允许这样做),引用类型,值类型。实例构造器用来设置一个类型某个实例的初始化状态,类型构造器用来设置一个类型的初始化状态。默认情况下,类型没有定义类型构造器。下面展示如何定义值类型和引用类型的构造器:

复制代码
internal  sealed  class SomeRefType  
{  
    static  SomeRefType()  
    {  
    }  
}  
  
internal  struct  SomeValType  
{    
    static  SomeValType()  
    {  
    }  
}
复制代码

可以发现一个特点是:无参,static标记,而且可访问性都是private,但是不能显示指定为private。当我们在值类型里面定义了一个类型构造器时,CLR不一定会调用这个静态构造器,如:

复制代码
    internal struct SomeValType
    {
        static SomeValType()
        {
            Console.WriteLine("不会展示出来");
        }
        public Int32 m_x;
    }

    public class Program
    {
        static void Main()
        {
            SomeValType[] a = new SomeValType[10];
            a[0].m_x = 123;
            Console.WriteLine(a[0].m_x);//显示123

        }
    }
复制代码

当JIT编译器编译一个方法时,它会检查在代码里面是否引入了其他类型。如果引入的的其他类型定义了类型构造器,则JIT会检测是否已经在AppDomain里面执行过。如果没有执行,则发起对类型构造器的调用,否则不调用。

在编译之后,线程会开始执行并最终获取调用构造器的代码。实际上有可能会是多个线程执行同一个方法,CLR想要确保一个类型构造器在一个AppDomain里面只执行一次,当一个类型构造器被调用时,调用的线程会获取一个互斥的线程同步锁,这时如果有其他的线程在调用,则会阻塞。当第一个线程执行完后离开,其他的线程被唤醒并发现构造器的代码执行过了,所以不会继续去执行了,从构造器方法返回。CLR通过这种方式来确保构造器仅仅被执行一次。

提示:

由于CLR会确保类型构造器在每一个AppDomain里面只会执行一次,是线程安全的。所以如果要初始化任何单例对象(singleton object),放在类型构造器里面是再合适不过了。

有这样一种情况,ClassA的类型构造器包含引用ClassB的代码,ClassB的类型构造器包含引用ClassA的代码,这种情况下CLR仍然会保证类型构造器只执行一次,但是应该尽量避免这种场景出现,因为这里应该避免调用类型构造器有了具体的顺序。

在类型构造器里面的代码仅仅访问的是类型的静态字段,通常的意图是初始化这些字段。C#提供了简单的语法来实现:

internal  sealed  class SomeType  
{  
    private static  Int32 s_x  = 5;   
}

上面的代码在生成时,编译器自动会为SomeType创建一个类型构造器如下:

复制代码
internal  sealed  class SomeType  
{  
    private static  Int32 s_x;  
    static  SomeType() 
    {
       s_x  = 5; 
    }  
}
复制代码

但是,C#不允许值类型使用内联字段初始化语法来实例化字段,所以下面这种方式就是错的:

internal  sealed struct SomeType  
{  
    private Int32 s_x  = 5; //这样会报错,需要加static关键字
}

如果显示的定义了类型构造器,如下:

复制代码
internal  sealed  class SomeType  
{  
    private static  Int32 s_x  = 5;   
    
    static  SomeType() 
    {  
         s_x  = 10;  
    }  
}
复制代码

最终s_x的结果是10。这里,C#编译器首先会生成一个类型构造器方法,这个构造器首先初始化s_x为5,然后初始化为10。换句话说,在类型构造器里面的显示定义的代码会在实例化静态字段之后执行。

类型构造器性能

调用类型构造器并不那么简单,JIT编译器不得不决定是否生成调用它的代码,并且CLR要确保调用是线程安全的。当编译器决定发起一个调用来执行类型构造器,它必须判断是否应该这样做,有两种可能性:

1.JIT在创建类型的第一个实例的代码之前立即发起或者在访问类的非继承的字段,成员的代码之前立即调用

2.JIT在首次访问一个静态字段,静态方法,实例方法,或调用一个实例构造器的代码之前某个时间调用,因为CLR要确保静态构造器在其他成员被访问之前运行。

 

注   《CLR via C#》(Jeffrey Richter著)——.NET 界的经典之作,读的过程写点笔记跟大家分享,我也推荐大家看英文版,能够直接领会原意 

本文转自Rt-张雪飞博客园博客,原文链接http://www.cnblogs.com/mszhangxuefei/archive/2012/09/18/clrnotes-6.html如需转载请自行联系原作者


张雪飞

相关文章
|
6月前
|
存储 安全 编译器
C# 11.0中的泛型属性:类型安全的新篇章
【1月更文挑战第23天】C# 11.0引入了泛型属性的概念,这一新特性为开发者提供了更高级别的类型安全性和灵活性。本文将详细探讨C# 11.0中泛型属性的工作原理、使用场景以及它们对现有编程模式的改进。通过深入了解泛型属性,开发者将能够编写更加健壮、可维护的代码,并充分利用C#语言的最新发展。
|
编译器 C#
C#之十七 局部类型
C#之十七 局部类型
32 0
|
13天前
|
编译器 C#
c# - 运算符<<不能应用于long和long类型的操作数
在C#中,左移运算符的第二个操作数必须是 `int`类型,因此需要将 `long`类型的位移计数显式转换为 `int`类型。这种转换需要注意数据丢失和负值处理的问题。通过本文的详细说明和示例代码,相信可以帮助你在实际开发中正确使用左移运算符。
23 3
|
12天前
|
编译器 C#
c# - 运算符<<不能应用于long和long类型的操作数
在C#中,左移运算符的第二个操作数必须是 `int`类型,因此需要将 `long`类型的位移计数显式转换为 `int`类型。这种转换需要注意数据丢失和负值处理的问题。通过本文的详细说明和示例代码,相信可以帮助你在实际开发中正确使用左移运算符。
27 1
|
10天前
|
编译器 C#
c# - 运算符<<不能应用于long和long类型的操作数
在C#中,左移运算符的第二个操作数必须是 `int`类型,因此需要将 `long`类型的位移计数显式转换为 `int`类型。这种转换需要注意数据丢失和负值处理的问题。通过本文的详细说明和示例代码,相信可以帮助你在实际开发中正确使用左移运算符。
8 0
|
1月前
|
C#
C# 可空类型(Nullable)
C# 单问号 ? 与 双问号 ??
46 12
|
3月前
|
存储 C#
揭秘C#.Net编程秘宝:结构体类型Struct,让你的数据结构秒变高效战斗机,编程界的新星就是你!
【8月更文挑战第4天】在C#编程中,结构体(`struct`)是一种整合多种数据类型的复合数据类型。与类不同,结构体是值类型,意味着数据被直接复制而非引用。这使其适合表示小型、固定的数据结构如点坐标。结构体默认私有成员且不可变,除非明确指定。通过`struct`关键字定义,可以包含字段、构造函数及方法。例如,定义一个表示二维点的结构体,并实现计算距离原点的方法。使用时如同普通类型,可通过实例化并调用其成员。设计时推荐保持结构体不可变以避免副作用,并注意装箱拆箱可能导致的性能影响。掌握结构体有助于构建高效的应用程序。
99 7
|
3月前
|
程序员 C#
C# 语言类型全解
C# 语言类型全解
23 0
|
3月前
|
开发框架 .NET 编译器
C# 中的记录(record)类型和类(class)类型对比总结
C# 中的记录(record)类型和类(class)类型对比总结
|
3月前
|
传感器 开发框架 JSON
聊聊 C# dynamic 类型,并分享一个将 dynamic 类型变量转为其它类型的技巧和实例
聊聊 C# dynamic 类型,并分享一个将 dynamic 类型变量转为其它类型的技巧和实例
152 0