gpt4 book ai didi

为什么应该尽可能避免在静态构造函数中初始化静态字段?

转载 作者:我是一只小鸟 更新时间:2023-07-10 14:34:09 27 4
gpt4 key购买 nike

C#具有一个默认开启的代码分析规则:[ CA1810 ]Initialize reference type static fields inline,推荐我们以内联的方式初始化静态字段,而不是将初始化放在静态构造函数中.

1、两种初始化的性能差异 2、beforefieldinit标记 3、静态构造函数执行的时机 4、关于“All-Zero”结构体 5、RuntimeHelpers.RunClassConstructor方法 。

1、两种初始化的性能差异

CA1810 这一规则与性能有关,我们可以利用如下这段简单的代码来演示两种初始化的性能差异。Foo和Bar这两个类的静态字段都定义了一个名为_value的静态字段,它们均通过调用静态方法Initialize返回的值进行初始化。不同的是Foo以内联(inline)赋值的方法进行初始化,而Bar则将初始化操作定义在静态构造函数中。假设Initialize方法是一个相对耗时的操作,我们利用Program的_initialized字段判断该方法是否被调用.

                          
                            static
                          
                          
                            class
                          
                           Program
{
    
                          
                            private
                          
                          
                            static
                          
                          
                            bool
                          
                           _initialized;
    
                          
                            static
                          
                          
                            void
                          
                           Main()
    {
        Foo.Invoke();
        Debug.Assert(_initialized == 
                          
                             false 
                          
                          );

        Bar.Invoke();
        Debug.Assert(_initialized == 
                          
                             true 
                          
                          );
    }
    
                          
                            private
                          
                          
                            static
                          
                          
                            int
                          
                           Initialize()
    {
        _initialized = 
                          
                            true
                          
                          ;
        
                          
                            return
                          
                           123;
    }
    
                          
                            public
                          
                          
                            class
                          
                           Foo
    {
        
                          
                            private
                          
                          
                            readonly
                          
                          
                            static
                          
                          
                            int
                          
                           _value = Initialize();
        
                          
                            public
                          
                          
                            static
                          
                          
                            int
                          
                           Value => _value;
        
                          
                            public
                          
                          
                            static
                          
                          
                            void
                          
                           Invoke() { }
    }
    
                          
                            public
                          
                          
                            class
                          
                           Bar
    {
        
                          
                            private
                          
                          
                            readonly
                          
                          
                            static
                          
                          
                            int
                          
                           _value;
        
                          
                            public
                          
                          
                            static
                          
                          
                            int
                          
                           Value => _value;
        
                          
                            static
                          
                           Bar() => _value = Initialize();
        
                          
                            public
                          
                          
                            static
                          
                          
                            void
                          
                           Invoke() { }
    }
}
                        

从我们给出的调用断言可以确定,当我们调用Foo的静态方法Invoke时,它的静态字段_value并没有初始化;但是当我们调用Bar的Invoke方法时,Initialize方法会率先被调用来初始化静态字段。从这个例子来说,由于整个应用并没有使用到Foo和Bar的静态字段,所以针对它们的初始化是没有必要的。所以我们说以内联方式对静态字段进行初始化的Foo具有更好的性能.

2、beforefieldinit标记

对于Foo和Bar这两个类型表现出来的不同行为,我们可以试着从IL代码层面寻找答案。如下所示的两段IL代码分别来源于Foo和Bar,我们可以看到虽然Foo类中没有显式定义静态构造函数,但是编译器会创建一个默认的静态构造函数,针对静态字段的初始化就放在这里。我们可以进一步看出,自动生成的这个静态构造函数和我们自己写的并没有本质的不同。两个类型之间的差异并没有体现在静态构造函数上,而是在于: 没有显式定义静态构造函数 的Foo类型上具有一个beforefieldinit标记.

                          

. class public auto ansi beforefieldinit Foo extends [System.Runtime]System.Object { .field private static initonly int 32 _value .method private hidebysig specialname rtspecialname static void .cctor () cil managed { .maxstack 8 IL_0000: call int 32 Program::Initialize() IL_0005: stsfld int 32 Foo::_value IL_000a: ret } 。

… } 。

                          .
                          
                            class
                          
                          
                            public
                          
                           auto ansi Bar
	extends [System.Runtime]System.Object
{

	.field 
                          
                            private
                          
                          
                            static
                          
                           initonly 
                          
                            int
                          
                          32 _value	
	.method 
                          
                            private
                          
                           hidebysig specialname rtspecialname 
                          
                            static
                          
                          
                            void
                          
                           .cctor () cil managed
	{
		.maxstack 8

		IL_0000: call 
                          
                            int
                          
                          32 Program::Initialize()
		IL_0005: stsfld 
                          
                            int
                          
                          32 Bar::_value
		IL_000a: ret
	} 
	
} 

                        

3、静态构造函数执行的时机

从Foo和Bar的IL代码可以看出,针对它们静态字段的初始化都放在静态构造函数中。但是当我们调用一个并不涉及类型静态字段的Invoke方法时,定义在Foo中的静态构造函数会自动执行,但是定义在Bar中的则不会,由此可以看出一个类型的静态构造函数的执行时机与类型是否具有beforefieldinit标记有关。具体规则如下,这一个规则直接定义在CLI标准 ECMA-335 中,静态构造函数在此标准中被称为类型初始化器(Type Initializer)或者.cctor.

  • 具有beforefieldinit标记:静态构造函数会在 第一次读取任何一个静态字段 之前自动执行,这相当于一种Lazy loading的模式;
  • 不具有beforefieldinit标记:静态构造函数会在如下场景下自动执行:
    • 第一次读取任何一个静态字段之前;
    • 第一个执行任何一个静态方法之前;
    • 引用类型:第一次调用构造函数之前;
    • 值类型:第一次调用实例方法;

由于beforefieldinit标记只有在没有显式定义静态构造函数的情况下才会被添加,所以我们自行定义的专门用来初始化静态字段的静态构造函数是完全没有必要的。不但没有必要,还可能带来性能问题,应该改成以内联的形式对静态字段进行初始化.

4、关于“All-Zero”结构体

如果我们在一个结构体中显式定义了一个静态构造函数,当我们调用其构造函数之前,静态构造函数会自动执行.

                          
                            public
                          
                          
                            class
                          
                           Program
{
    
                          
                            private
                          
                          
                            static
                          
                          
                            bool
                          
                           _initialized= 
                          
                            false
                          
                          ;

    
                          
                            static
                          
                          
                            void
                          
                           Main()
    {
        var foobar = 
                          
                            new
                          
                           Foobar(1, 2);
        Debug.Assert(_initialized == 
                          
                             true 
                          
                          );
    }

    
                          
                            public
                          
                          
                            struct
                          
                           Foobar
    {
        
                          
                            static
                          
                           Foobar() => _initialized = 
                          
                            true
                          
                          ;
        
                          
                            public
                          
                           Foobar(
                          
                            int
                          
                           foo, 
                          
                            int
                          
                           bar)
        {
            Foo = foo;
            Bar = bar;
        }

        
                          
                            public
                          
                          
                            int
                          
                           Foo { 
                          
                            get
                          
                          ; }
        
                          
                            public
                          
                          
                            int
                          
                           Bar { 
                          
                            get
                          
                          ; }
    }
}
                        

倘若按照如下的方式利用default关键字得到一个所有字段为“零”的默认结构体(all-zero structure),我们显式定义的静态构造函数是不会执行的.

                          
                            public
                          
                          
                            class
                          
                           Program
{
    
                          
                            private
                          
                          
                            static
                          
                          
                            bool
                          
                           _initialized = 
                          
                            false
                          
                          ;

    
                          
                            static
                          
                          
                            void
                          
                           Main()
    {
        Foobar foobar = 
                          
                             default 
                          
                          ;
        Debug.Assert(foobar.Foo == 0);
        Debug.Assert(foobar.Bar == 0);
        Debug.Assert(_initialized == 
                          
                             false 
                          
                          );
    }
    ...
}
                        

5、RuntimeHelpers.RunClassConstructor方法

如果我们要确保某个类型的静态构造函数已经被显式调用,可以执行RuntimeHelpers.RunClassConstructor方法,它的参数为目标类型的TypeHandle.

                          

public class Program { private static bool _initialized = false ; static void Main() { RuntimeHelpers.RunClassConstructor( typeof (Foobar).TypeHandle); Debug.Assert(_initialized == true ); } 。

… } 。

由于类型的静态构造函数只会被执行一次,所以多次RuntimeHelpers.RunClassConstructor并不会导致静态函数的重复执行.

                          

public class Program { private static bool _initialized = false ; static void Main() { RuntimeHelpers.RunClassConstructor( typeof (Foobar).TypeHandle); Debug.Assert(_initialized == true ); _typeInitializerInvoked = false ; RuntimeHelpers.RunClassConstructor( typeof (Foobar).TypeHandle); Debug.Assert(_initialized == false ); } ... } 。

最后此篇关于为什么应该尽可能避免在静态构造函数中初始化静态字段?的文章就讲到这里了,如果你想了解更多关于为什么应该尽可能避免在静态构造函数中初始化静态字段?的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com