静态只读字段初始化程序与静态构造函数初始化

49

以下是初始化静态只读字段的两种不同方法。这两种方法有什么区别吗?如果有,那么应该在什么情况下选择其中之一?

class A
{
    private static readonly string connectionString =
        WebConfigurationManager.ConnectionStrings["SomeConnection"].ConnectionString;
}

class B
{
    private static readonly string connectionString;

    static B()
    {
        connectionString =
            WebConfigurationManager.ConnectionStrings["SomeConnection"].ConnectionString;
    }
}
4个回答

42
这两者之间有一个微妙的区别,在IL代码中可以看到 - 明确放置静态构造函数告诉C#编译器不要将类型标记为beforefieldinit。beforefieldinit会影响类型初始化程序的运行时间,了解这一点对于编写C#中的lazy singletons非常有用。

简而言之,区别在于:

.class private auto ansi beforefieldinit A
.class private auto ansi B

在其他方面它们是相同的。从反编译器输出:

A类:

.class private auto ansi beforefieldinit A
    extends [mscorlib]System.Object
{
    .method private hidebysig specialname rtspecialname static void .cctor() cil managed
    {
        .maxstack 8
        L_0000: ldsfld class [mscorlib]System.Collections.Generic.Dictionary`2<string, class Connection> WebConfigurationManager::ConnectionStrings
        L_0005: ldstr "SomeConnection"
        L_000a: callvirt instance !1 [mscorlib]System.Collections.Generic.Dictionary`2<string, class Connection>::get_Item(!0)
        L_000f: ldfld string Connection::ConnectionString
        L_0014: stsfld string A::connectionString
        L_0019: ret 
    }

    .method public hidebysig specialname rtspecialname instance void .ctor() cil managed
    {
        .maxstack 8
        L_0000: ldarg.0 
        L_0001: call instance void [mscorlib]System.Object::.ctor()
        L_0006: ret 
    }

    .field private static initonly string connectionString
} 

类 B:

.class private auto ansi B
    extends [mscorlib]System.Object
{
    .method private hidebysig specialname rtspecialname static void .cctor() cil managed
    {
        .maxstack 8
        L_0000: nop 
        L_0001: ldsfld class [mscorlib]System.Collections.Generic.Dictionary`2<string, class Connection> WebConfigurationManager::ConnectionStrings
        L_0006: ldstr "SomeConnection"
        L_000b: callvirt instance !1 [mscorlib]System.Collections.Generic.Dictionary`2<string, class Connection>::get_Item(!0)
        L_0010: ldfld string Connection::ConnectionString
        L_0015: stsfld string B::connectionString
        L_001a: ret 
}

    .method public hidebysig specialname rtspecialname instance void .ctor() cil managed
    {
        .maxstack 8
        L_0000: ldarg.0 
        L_0001: call instance void [mscorlib]System.Object::.ctor()
        L_0006: ret 
    }


    .field private static initonly string connectionString    
}

19

beforefieldinit属性指示初始化发生的方式。

在使用显式静态构造函数初始化的情况下,静态成员的初始化发生在类型被访问的那一刻。例如,在类A的示例中,只有在首次引用connectionString时才会进行初始化,而在类B的示例中,初始化将在第一次引用类B时发生,不一定是访问connectionString

只有C# (.NET 4.0)提供了控制静态成员如何初始化的功能。而VB.NET仅支持非beforefieldinit方法,而C++/CLI仅支持beforefieldinit机制。


在类A的示例中,只有在第一次引用connectionString时才会进行初始化。我认为这可以概括为“只有在首次引用类A的任何静态字段时才会进行初始化”。 - Sivaram Koduri

7

它们本质上是相同的,但如果你恰好有一个对静态字段的只读赋值和一个静态类型构造函数并且,则只读赋值先发生。


2
我必须补充一点,如果存在显式构造函数(非 beforefieldinit 版本),访问静态成员会比较慢。
来自https://rules.sonarsource.com/csharp/RSPEC-3963 当静态构造函数仅用于初始化静态字段时,它会带来不必要的性能开销,因为编译器在每次静态方法或实例构造函数调用之前生成一个检查。因此,强烈建议使用内联初始化。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接