《CLR Via C# 第3版》筆記之(四) - 類中字段的默認(rèn)賦值
在C#中,除了可以在類的構(gòu)造函數(shù)中初始化私有字段的值,還可以在私有字段定義的地方進(jìn)行初始化(即默認(rèn)賦值)。下面討論默認(rèn)賦值和在構(gòu)造函數(shù)中賦值的區(qū)別,以便更好的在代碼中使用這兩種賦值。
主要內(nèi)容:
- 對(duì)代碼生成的影響
- 對(duì)代碼執(zhí)行的影響
1. 對(duì)代碼生成的影響
首先構(gòu)造兩個(gè)Class,其中ClassA使用默認(rèn)賦值的方式,ClassB使用構(gòu)造函數(shù)賦值的方式。
代碼如下:
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代碼,發(fā)現(xiàn)ClassA生成的代碼比較多。即每個(gè)構(gòu)造函數(shù)開(kāi)始執(zhí)行處,都會(huì)將字段的默認(rèn)賦值生成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
由此看出,雖然默認(rèn)賦值的方法比較直觀和方便,但是從生成的代碼來(lái)看,默認(rèn)賦值的方法會(huì)導(dǎo)致代碼膨脹,所以不應(yīng)在以下場(chǎng)合使用:
1)字段比較多的Class
2)構(gòu)造函數(shù)有多個(gè)重載版本
2. 對(duì)代碼執(zhí)行的影響
通過(guò)上面的IL代碼,我們發(fā)現(xiàn)默認(rèn)賦值除了會(huì)導(dǎo)致代碼膨脹,賦值的時(shí)機(jī)也和在構(gòu)造函數(shù)中對(duì)字段的賦值不一樣。
我們知道,類的構(gòu)造函數(shù)在執(zhí)行之前,都會(huì)調(diào)用其基類的構(gòu)造函數(shù),由于所以類都默認(rèn)繼承System.Object,所以上面的ClassA和ClassB雖然沒(méi)有指定基類,
但都繼承于System.Object,所以都會(huì)調(diào)用System.Object的構(gòu)造函數(shù)。
調(diào)用System.Object的構(gòu)造函數(shù)的IL代碼即為:call instance void object::.ctor()從上面的IL代碼中,我們發(fā)現(xiàn):
1)默認(rèn)賦值方式是在調(diào)用System.Object的構(gòu)造函數(shù)前給字段賦值的
2)構(gòu)造函數(shù)中賦值方式是在調(diào)用System.Object的構(gòu)造函數(shù)后給字段賦值的 這里的差別雖然很小,但是有時(shí)卻會(huì)導(dǎo)致代碼產(chǎn)生不同的結(jié)果,從而帶來(lái)潛在的bug。
這兩種賦值方式在什么情況下會(huì)導(dǎo)致執(zhí)行結(jié)果不同呢?
根據(jù)其賦值時(shí)機(jī)的不同,我們可以推斷在如下情況下,兩種賦值方式的執(zhí)行結(jié)果不同。
基類中調(diào)用虛方法并且如果子類覆蓋(override)了此虛方法,那么此虛方法中的字段就有可能已經(jīng)初始化或者未初始化。
第一種情況 (默認(rèn)賦值的方式)
class Test
{
static void Main()
{
// 第一步:調(diào)用SubClass的構(gòu)造函數(shù)
SubClass sub = new SubClass();
Console.ReadKey(true);
}
}
public class BaseClass
{
// 第四步:調(diào)用基類構(gòu)造函數(shù),其中虛方法Print已經(jīng)被子類覆蓋
public BaseClass()
{
Print();
}
public virtual void Print()
{
Console.WriteLine("Base class initilized!");
}
}
public class SubClass : BaseClass
{
// 第三步:對(duì)sub_a,sub_b,obj進(jìn)行賦值,然后再調(diào)用基類構(gòu)造函數(shù)
private Int32 sub_a = 123;
private String sub_b = "abc";
private Object obj = new object();
// 第二步:由于是默認(rèn)賦值的方式,所以先將sub_a,sub_b,obj賦值后再調(diào)用基類構(gòu)造函數(shù)
public SubClass()
{
}
// 第五步:調(diào)用被覆蓋的Print方法,由于obj已被賦值,所以進(jìn)入else分支去執(zhí)行
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!");
}
}
}
執(zhí)行結(jié)果如下,執(zhí)行過(guò)程可以參見(jiàn)上面代碼中的注釋
第二種情況(構(gòu)造函數(shù)中對(duì)字段賦值的方式)
class Test
{
static void Main()
{
// 第一步:調(diào)用SubClass的構(gòu)造函數(shù)
SubClass sub = new SubClass();
Console.ReadKey(true);
}
}
public class BaseClass
{
// 第三步:調(diào)用基類構(gòu)造函數(shù),其中虛方法Print已經(jīng)被子類覆蓋
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;
// 第二步:由于是在構(gòu)造函數(shù)對(duì)字段輔助的方式,所以先默認(rèn)調(diào)用基類構(gòu)造函數(shù)
public SubClass()
{
// 第五步:基類構(gòu)造函數(shù)執(zhí)行完后,進(jìn)入下面的賦值
sub_a = 123;
sub_b = "abc";
obj = new object();
}
// 第四步:調(diào)用被覆蓋的Print方法,由于obj還未被賦值,所以進(jìn)入if分支去執(zhí)行
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!");
}
}
}
執(zhí)行結(jié)果如下,執(zhí)行過(guò)程可以參見(jiàn)上面代碼中的注釋
小小的賦值,也會(huì)導(dǎo)致意外的bug。所以我們?cè)谑褂脮r(shí)默認(rèn)賦值時(shí)一定要對(duì)其賦值的時(shí)機(jī)做到心中有數(shù)。

浙公網(wǎng)安備 33010602011771號(hào)