C#《CLRAV4 Via C# 第二版》笔记之(四) – 类中字段的私下认可赋值

在C#中,除了能够在类的构造函数中伊始化私有字段的值,还是能够在个体字段定义的地点举行初叶化(C#,即私下认可赋值)。下边商讨暗中同意赋值和在构造函数中赋值的区分,以便更好的在代码中央银行使这三种赋值。

重庆大学内容:

  • 对代码生成的熏陶
  • 对代码执行的影响

1. 对代码生成的熏陶

率先构造五个Class,当中ClassA使用私下认可赋值的点子,ClassB使用构造函数赋值的点子。

代码如下:

    public class ClassA
    {
        private Int32 a = 123;
        private String b = "abc";
        private Object c = new object();

        public ClassA()
        {
        }
        public ClassA(int aa)
        {
            a = aa;
        }
    }

    public class ClassB
    {
        private Int32 a;
        private String b;
        private Object c;

        public ClassB()
        {
            a = 123;
            b = "abc";
            c = new object();
        }
        public ClassB(int aa)
        {
            a = aa;
        }
    } 

编译成dll后,再用ILSpy查看其IL代码,发现ClassA生成的代码相比较多。即各种构造函数开首实施处,都会将字段的私下认可赋值生成IL代码插入当中。

ClassA IL代码如下

.class public auto ansi beforefieldinit ClassA
    extends object
{
    // Fields
    .field private int32 a
    .field private string b
    .field private object c

    // Methods
    .method public hidebysig specialname rtspecialname 
        instance void .ctor () cil managed 
    {
        // Method begins at RVA 0x2073
        // Code size 40 (0x28)
        .maxstack 8

        IL_0000: ldarg.0
        IL_0001: ldc.i4.s 123
        IL_0003: stfld int32 class cnblog_bowen.ClassA::a
        IL_0008: ldarg.0
        IL_0009: ldstr "abc"
        IL_000e: stfld string class cnblog_bowen.ClassA::b
        IL_0013: ldarg.0
        IL_0014: newobj instance void object::.ctor()
        IL_0019: stfld object class cnblog_bowen.ClassA::c
        IL_001e: ldarg.0
        IL_001f: call instance void object::.ctor()
        IL_0024: nop
        IL_0025: nop
        IL_0026: nop
        IL_0027: ret
    } // End of method ClassA..ctor

    .method public hidebysig specialname rtspecialname 
        instance void .ctor (
            int32 aa
        ) cil managed 
    {
        // Method begins at RVA 0x209c
        // Code size 47 (0x2f)
        .maxstack 8

        IL_0000: ldarg.0
        IL_0001: ldc.i4.s 123
        IL_0003: stfld int32 class cnblog_bowen.ClassA::a
        IL_0008: ldarg.0
        IL_0009: ldstr "abc"
        IL_000e: stfld string class cnblog_bowen.ClassA::b
        IL_0013: ldarg.0
        IL_0014: newobj instance void object::.ctor()
        IL_0019: stfld object class cnblog_bowen.ClassA::c
        IL_001e: ldarg.0
        IL_001f: call instance void object::.ctor()
        IL_0024: nop
        IL_0025: nop
        IL_0026: ldarg.0
        IL_0027: ldarg.1
        IL_0028: stfld int32 class cnblog_bowen.ClassA::a
        IL_002d: nop
        IL_002e: ret
    } // End of method ClassA..ctor

} // End of class cnblog_bowen.ClassA

* * 

ClassB IL代码如下

.class public auto ansi beforefieldinit ClassB
    extends object
{
    // Fields
    .field private int32 a
    .field private string b
    .field private object c

    // Methods
    .method public hidebysig specialname rtspecialname 
        instance void .ctor () cil managed 
    {
        // Method begins at RVA 0x20cc
        // Code size 40 (0x28)
        .maxstack 8

        IL_0000: ldarg.0
        IL_0001: call instance void object::.ctor()
        IL_0006: nop
        IL_0007: nop
        IL_0008: ldarg.0
        IL_0009: ldc.i4.s 123
        IL_000b: stfld int32 class cnblog_bowen.ClassB::a
        IL_0010: ldarg.0
        IL_0011: ldstr "abc"
        IL_0016: stfld string class cnblog_bowen.ClassB::b
        IL_001b: ldarg.0
        IL_001c: newobj instance void object::.ctor()
        IL_0021: stfld object class cnblog_bowen.ClassB::c
        IL_0026: nop
        IL_0027: ret
    } // End of method ClassB..ctor

    .method public hidebysig specialname rtspecialname 
        instance void .ctor (
            int32 aa
        ) cil managed 
    {
        // Method begins at RVA 0x20f5
        // Code size 17 (0x11)
        .maxstack 8

        IL_0000: ldarg.0
        IL_0001: call instance void object::.ctor()
        IL_0006: nop
        IL_0007: nop
        IL_0008: ldarg.0
        IL_0009: ldarg.1
        IL_000a: stfld int32 class cnblog_bowen.ClassB::a
        IL_000f: nop
        IL_0010: ret
    } // End of method ClassB..ctor

} // End of class cnblog_bowen.ClassB

通过看出,固然默许赋值的章程比较直观和惠及,可是从扭转的代码来看,私下认可赋值的艺术会造成代码膨胀,所以不应在以下地方使用:

1)字段比较多的Class

2)构造函数有多个重载版本

2. 对代码执行的熏陶

经过地点的IL代码,大家发现私下认可赋值除了会促成代码膨胀,赋值的时机也和在构造函数中对字段的赋值分歧。

笔者们掌握,类的构造函数在推行从前,都会调用其基类的构造函数,由于所以类都暗中同意继承System.Object,所以地方的ClassA和ClassB尽管尚未点名基类,

但都再而三于System.Object,所以都会调用System.Object的构造函数。

调用System.Object的构造函数的IL代码即为:call instance void
object::.ctor()从上边的IL代码中,大家发现:

1)暗中认可赋值方式是在调用System.Object的构造函数前给字段赋值的

2)构造函数中赋值方式是在调用System.Object的构造函数后给字段赋值的
那里的不一样就算非常的小,可是有时却会招致代码发生分歧的结果,从而拉动潜在的bug。

那三种赋值格局在哪些状态下会促成执行结果区别吧?

遵照其赋值时机的不比,我们能够臆想在如下境况下,二种赋值情势的执行结果分裂。

基类中调用虚方法并且只要子类覆盖(override)了此虚方法,那么此虚方法中的字段就有大概已经初叶化或许未早先化。

先是种处境 (暗中同意赋值的章程)

    class Test
    {
        static void Main()
        {
            // 第一步:调用SubClass的构造函数
            SubClass sub = new SubClass();

            Console.ReadKey(true);
        }
    }

    public class BaseClass
    {
        // 第四步:调用基类构造函数,其中虚方法Print已经被子类覆盖
        public BaseClass()
        {
            Print();
        }

        public virtual void Print()
        {
            Console.WriteLine("Base class initilized!");
        }
    }

    public class SubClass : BaseClass
    {
        // 第三步:对sub_a,sub_b,obj进行赋值,然后再调用基类构造函数
        private Int32 sub_a = 123;
        private String sub_b = "abc";
        private Object obj = new object();

        // 第二步:由于是默认赋值的方式,所以先将sub_a,sub_b,obj赋值后再调用基类构造函数
        public SubClass()
        {
        }

        // 第五步:调用被覆盖的Print方法,由于obj已被赋值,所以进入else分支去执行
        public override void Print()
        {
            if (null == obj)
                Console.WriteLine("Sub class is uninitilize!");
            else
            {
                Console.WriteLine("a= " + sub_a);
                Console.WriteLine("b= " + sub_b);
                Console.WriteLine("Sub class was initilized!");
            }
        }
    }

进行结果如下,执行进度能够参见上边代码中的注释

C# 1

其次种处境(构造函数中对字段赋值的办法)

    class Test
    {
        static void Main()
        {
            // 第一步:调用SubClass的构造函数
            SubClass sub = new SubClass();

            Console.ReadKey(true);
        }
    }

    public class BaseClass
    {
        // 第三步:调用基类构造函数,其中虚方法Print已经被子类覆盖
        public BaseClass()
        {
            Print();
        }

        public virtual void Print()
        {
            Console.WriteLine("Base class initilized!");
        }
    }

    public class SubClass : BaseClass
    {
        private Int32 sub_a;
        private String sub_b;
        private Object obj;

        // 第二步:由于是在构造函数对字段辅助的方式,所以先默认调用基类构造函数
        public SubClass()
        {
            // 第五步:基类构造函数执行完后,进入下面的赋值
            sub_a = 123;
            sub_b = "abc";
            obj = new object();
        }

        // 第四步:调用被覆盖的Print方法,由于obj还未被赋值,所以进入if分支去执行
        public override void Print()
        {
            if (null == obj)
                Console.WriteLine("Sub class is uninitilize!");
            else
            {
                Console.WriteLine("a= " + sub_a);
                Console.WriteLine("b= " + sub_b);
                Console.WriteLine("Sub class was initilized!");
            }
        }
    }

举行理并了结果如下,执行进度能够瞻仰下边代码中的注释

C# 2

细微赋值,也会招致意外的bug。所以我们在利用时暗中同意赋值时一定要对其赋值的机遇做到心中有数。

相关文章