Подтвердить что ты не робот

Статический инициализатор поля readonly vs инициализация статического конструктора

Ниже приведены два способа инициализации статических полей readonly. Есть ли разница между двумя подходами? Если да, то когда нужно быть предпочтительнее другого?

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;
    }
}
4b9b3361

Ответ 1

Существует одно тонкое различие между этими двумя, которое можно увидеть в коде IL - помещение явного статического конструктора сообщает компилятору С# не отмечать тип как beforefieldinit. Функция beforefieldinit влияет, когда запускается инициализатор типа, и знание об этом полезно, например, при написании ленивых одноэлементов в С#.

Вкратце разница заключается в следующем:

.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    
}

Ответ 2

Атрибут beforefieldinit указывает, как происходит инициализация.

В случае явной инициализации статического конструктора инициализация статического члена происходит в момент обращения к типу. В примере, приведенном в случае класса A, инициализация произойдет только тогда, когда сначала будет connectionString, тогда как в случае инициализации класса B произойдет первый раз, когда будет передан тип класса B, не обязательно доступ к ConnectionString.

Только С# (.NET 4.0) предоставляет нам контроль над тем, как статические члены могут быть инициализированы. С VB.NET возможен только метод non beforefieldinit, тогда как с С++/CLI возможен только механизм beforefieldinit.

Ответ 3

Они по существу одинаковы, но если у вас есть как назначение только для чтения для статического поля, так и конструктор статического типа, сначала выполняется назначение только для чтения.