C# 构造函数避免IL(反编译)代码膨胀的方法--C#编译有点狂啊
2017-03-31 12:42
761 查看
构造器(constructor) 是允许将类型的实例化初始化为良好状态的一种特殊方法。构造器方法在”方法定义元数据表”中始终叫.ctor(代表constructor)。创建一个引用类型的实例时,首先为实例的数据字段分配内存,然后初始化对象的附加字段(类型对象指针和同步块索引),最后调用类型的实例构造器来设置对象的初始状态。
构造引用类型的对象时,在调用类型的实例构造器之前,为对象分配的内存总是先被归零。构造器没有显式重写的所有字段保证都有一个0和null。
实例构造器永远不能被继承。不能有如下的修饰符:virtual,new,override,sealed和abstract。如果定义的类没有显式定义任何构造器,C# 编译器将定义一个默认(无参)构造器。在它的实现中,只是简单地调用了基类的无参构造器。
例如:
等价于
如果类的修饰符为abstract,那么编译器生成的默认构造参数的可访问性为protected;否则构造器会被赋予public可访问类。如果基类没有提供无参构造器,那么派生类必须显式调用一个基类的构造器,否则编译器会报错。如果类的修饰符为static(sealed 和abstract),编译器根本不会在类定义中生成一个默认构造器。
一个了类型可以定义多个实例构造器。每个构造器都必须有一个不同的签名,而且每个都可以有不同的可访问性。为了使代码可验证,类的实例构造器在访问从基类继承的任何字段之前,必须先调用基类的构造器。如果派生类的构造器没有显式调用一个基类无参构造器,C#编译器会自动生成对默认的基类构造器的调用。最终,System.Object的公共无参构造器会得到调用。该构造器什么都不做,会直接返回。由于System.Object没有定义实例数据字段,所以它的构造器无事可做。
在极少数情况下,可以在不调用实例构造器的前提下创建一个类型的实例。一个典型的例子是Objcet的MemberwiseClone方法。该方法的作用是分配内存,初始化对象的附加字段(类型对象指针和同步块索引),然后将源对象的字节数据复制到新对象中。另外,用运行时序列化器反序列化对象时,通常也不需要调用构造函数。反序列化代码使用System.Runtime.Serialization.FormatterServices类型的GetUninitializedObject或者GetSafeUninitializedObject方法为对象分配内存,期间不用调用构造器。
不要在构造器中调用会影响所构造对象的任何虚方法.原因是假如这个虚方法在当前要实例化的类型的派生类型中进行了重写,就会调用重写的实现。但在继承层次结构中,字段尚未初始化。所以,调用虚方法将导致无法预测的行为 (可以看看我之前写的这篇关于继承理解的文章:http://blog.csdn.net/u010533180/article/details/52709684)
C# 语言提供了一个简单的语法,允许在构造引用类型的一个实例时,对类型中定义的字段进行初始化。
构造ConstructorDemo的对象时,它的字段m_x字段初始化为5。我们看一下对应的IL代码:(IL是VS自带的反编译工具,读者可自行查阅)
通过反编译可看到ConstructorDemo的构造器把值5存储到字段m_x,在调用基类(Object)的构造器。
C# 提供了这样简化的语法,允许以内联方式初始化实例字段。但在幕后,它会将这种语法转换成构造器方法中的代码来执行。这同时提醒我们注意代码的膨胀效益。何为膨胀?我们通过下面的例子来看。重点和知识点来了。
首先用一个无参构造函数和一个int构造函数来看IL代码:
对应的IL代码为:
通过上面的两端IL代码 我们还没有发现有什么问题,现在我们在增加一个构造函数。
我们现在只看新增这个string类型对应的IL源码:
我们和上面的int构造函数的IL代码对比发现,有很多相似的IL,我们在增加一个int和string的类型的构造函数。
我们看一下对应的IL代码:
在看这个对应的IL代码发现其实和上面的几个构造函数有很多重复的代码,即初始化变量。如果在新增一个字段或者新增一个构造函数,就意味每个构造函数都会有对应重复的变量初始化,尤其是新增内联字段,代码会增加的更多。如果每个构造函数对字段值进行赋值操作,那么对应生成的IL代码会更多(读者可自行尝试)这就是代码的膨胀。
对应的解决方案就是创建单个构造器来执行这些公共初始化。然后让其它构造器显式调用这个公共初始化构造器。这样可以减少生成的代码。在C#中利用this关键字来显式调用另一个构造器:对上面的代码修改如下:
对应的IL代码如下:
读者可以对比一下编译生成的代码少了很多。这是很推荐的一种写法。
我在附上几幅对比图,会更加明了(左侧统一为ConstructorDemo源码,右侧统一为ConstructorDemo2):
无参构造函数IL代码对比图
int构造函数IL代码对比图
string构造函数IL代码对比图
int,string构造函数IL代码对比图
参考资料:
CLR VIA C#(第3版)
构造引用类型的对象时,在调用类型的实例构造器之前,为对象分配的内存总是先被归零。构造器没有显式重写的所有字段保证都有一个0和null。
实例构造器永远不能被继承。不能有如下的修饰符:virtual,new,override,sealed和abstract。如果定义的类没有显式定义任何构造器,C# 编译器将定义一个默认(无参)构造器。在它的实现中,只是简单地调用了基类的无参构造器。
例如:
public class TestClass{}
等价于
public class TestClass{ public TestClass():base(){} }
如果类的修饰符为abstract,那么编译器生成的默认构造参数的可访问性为protected;否则构造器会被赋予public可访问类。如果基类没有提供无参构造器,那么派生类必须显式调用一个基类的构造器,否则编译器会报错。如果类的修饰符为static(sealed 和abstract),编译器根本不会在类定义中生成一个默认构造器。
一个了类型可以定义多个实例构造器。每个构造器都必须有一个不同的签名,而且每个都可以有不同的可访问性。为了使代码可验证,类的实例构造器在访问从基类继承的任何字段之前,必须先调用基类的构造器。如果派生类的构造器没有显式调用一个基类无参构造器,C#编译器会自动生成对默认的基类构造器的调用。最终,System.Object的公共无参构造器会得到调用。该构造器什么都不做,会直接返回。由于System.Object没有定义实例数据字段,所以它的构造器无事可做。
在极少数情况下,可以在不调用实例构造器的前提下创建一个类型的实例。一个典型的例子是Objcet的MemberwiseClone方法。该方法的作用是分配内存,初始化对象的附加字段(类型对象指针和同步块索引),然后将源对象的字节数据复制到新对象中。另外,用运行时序列化器反序列化对象时,通常也不需要调用构造函数。反序列化代码使用System.Runtime.Serialization.FormatterServices类型的GetUninitializedObject或者GetSafeUninitializedObject方法为对象分配内存,期间不用调用构造器。
不要在构造器中调用会影响所构造对象的任何虚方法.原因是假如这个虚方法在当前要实例化的类型的派生类型中进行了重写,就会调用重写的实现。但在继承层次结构中,字段尚未初始化。所以,调用虚方法将导致无法预测的行为 (可以看看我之前写的这篇关于继承理解的文章:http://blog.csdn.net/u010533180/article/details/52709684)
C# 语言提供了一个简单的语法,允许在构造引用类型的一个实例时,对类型中定义的字段进行初始化。
class ConstructorDemo { private int m_x = 5; }
构造ConstructorDemo的对象时,它的字段m_x字段初始化为5。我们看一下对应的IL代码:(IL是VS自带的反编译工具,读者可自行查阅)
.method public hidebysig specialname rtspecialname instance void .ctor() cil managed { // 代码大小 15 (0xf) .maxstack 8 IL_0000: ldarg.0 IL_0001: ldc.i4.5 IL_0002: stfld int32 ConstructorDemo::m_x IL_0007: ldarg.0 IL_0008: call instance void [mscorlib]System.Object::.ctor() //隐式调用了Object的构造函数 IL_000d: nop IL_000e: ret } // end of method ConstructorDemo::.ctor
通过反编译可看到ConstructorDemo的构造器把值5存储到字段m_x,在调用基类(Object)的构造器。
C# 提供了这样简化的语法,允许以内联方式初始化实例字段。但在幕后,它会将这种语法转换成构造器方法中的代码来执行。这同时提醒我们注意代码的膨胀效益。何为膨胀?我们通过下面的例子来看。重点和知识点来了。
首先用一个无参构造函数和一个int构造函数来看IL代码:
class ConstructorDemo { private int m_x = 5; private string m_s = "hello world"; private double m_d = 3.14159; private byte m_b = 0xff; public ConstructorDemo() { } public ConstructorDemo(int x) { m_x = x; } }
对应的IL代码为:
无参构造函数的IL: .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { // 代码大小 54 (0x36) .maxstack 8 IL_0000: ldarg.0 IL_0001: ldc.i4.5 IL_0002: stfld int32 NowCoderProgrammingProject.ConstructorDemo::m_x IL_0007: ldarg.0 IL_0008: ldstr "hello world" IL_000d: stfld string NowCoderProgrammingProject.ConstructorDemo::m_s IL_0012: ldarg.0 IL_0013: ldc.r8 3.1415899999999999 IL_001c: stfld float64 NowCoderProgrammingProject.ConstructorDemo::m_d IL_0021: ldarg.0 IL_0022: ldc.i4 0xff IL_0027: stfld uint8 NowCoderProgrammingProject.ConstructorDemo::m_b IL_002c: ldarg.0 IL_002d: call instance void [mscorlib]System.Object::.ctor() IL_0032: nop IL_0033: nop IL_0034: nop IL_0035: ret } // end of method ConstructorDemo::.ctor int构造函数的IL代码: .method public hidebysig specialname rtspecialname instance void .ctor(int32 x) cil managed { // 代码大小 61 (0x3d) .maxstack 8 IL_0000: ldarg.0 IL_0001: ldc.i4.5 IL_0002: stfld int32 NowCoderProgrammingProject.ConstructorDemo::m_x IL_0007: ldarg.0 IL_0008: ldstr "hello world" IL_000d: stfld string NowCoderProgrammingProject.ConstructorDemo::m_s IL_0012: ldarg.0 IL_0013: ldc.r8 3.1415899999999999 IL_001c: stfld float64 NowCoderProgrammingProject.ConstructorDemo::m_d IL_0021: ldarg.0 IL_0022: ldc.i4 0xff IL_0027: stfld uint8 NowCoderProgrammingProject.ConstructorDemo::m_b IL_002c: ldarg.0 IL_002d: call instance void [mscorlib]System.Object::.ctor() IL_0032: nop IL_0033: nop IL_0034: ldarg.0 IL_0035: ldarg.1 IL_0036: stfld int32 NowCoderProgrammingProject.ConstructorDemo::m_x IL_003b: nop IL_003c: ret } // end of method ConstructorDemo::.ctor
通过上面的两端IL代码 我们还没有发现有什么问题,现在我们在增加一个构造函数。
class ConstructorDemo { private int m_x = 5; private string m_s = "hello world"; private double m_d = 3.14159; private byte m_b = 0xff; public ConstructorDemo() { } public ConstructorDemo(int x) { m_x = x; } public ConstructorDemo(string s) { m_s = s; m_d = 10; } }
我们现在只看新增这个string类型对应的IL源码:
.method public hidebysig specialname rtspecialname instance void .ctor(string s) cil managed { // 代码大小 76 (0x4c) .maxstack 2 IL_0000: ldarg.0 IL_0001: ldc.i4.5 IL_0002: stfld int32 NowCoderProgrammingProject.ConstructorDemo::m_x IL_0007: ldarg.0 IL_0008: ldstr "hello world" IL_000d: stfld string NowCoderProgrammingProject.ConstructorDemo::m_s IL_0012: ldarg.0 IL_0013: ldc.r8 3.1415899999999999 IL_001c: stfld float64 NowCoderProgrammingProject.ConstructorDemo::m_d IL_0021: ldarg.0 IL_0022: ldc.i4 0xff IL_0027: stfld uint8 NowCoderProgrammingProject.ConstructorDemo::m_b IL_002c: ldarg.0 IL_002d: call instance void [mscorlib]System.Object::.ctor() IL_0032: nop IL_0033: nop IL_0034: ldarg.0 IL_0035: ldarg.1 IL_0036: stfld string NowCoderProgrammingProject.ConstructorDemo::m_s IL_003b: ldarg.0 IL_003c: ldc.r8 10. IL_0045: stfld float64 NowCoderProgrammingProject.ConstructorDemo::m_d IL_004a: nop IL_004b: ret } // end of method ConstructorDemo::.ctor
我们和上面的int构造函数的IL代码对比发现,有很多相似的IL,我们在增加一个int和string的类型的构造函数。
class ConstructorDemo { private int m_x = 5; private string m_s = "hello world"; private double m_d = 3.14159; private byte m_b = 0xff; public ConstructorDemo() { } public ConstructorDemo(int x) { m_x = x; } public ConstructorDemo(string s) { m_s = s; m_d = 10; } public ConstructorDemo(int x, string s) { m_x = x; m_s = s; } }
我们看一下对应的IL代码:
.method public hidebysig specialname rtspecialname instance void .ctor(int32 x, string s) cil managed { // 代码大小 68 (0x44) .maxstack 2 IL_0000: ldarg.0 IL_0001: ldc.i4.5 IL_0002: stfld int32 NowCoderProgrammingProject.ConstructorDemo::m_x IL_0007: ldarg.0 IL_0008: ldstr "hello world" IL_000d: stfld string NowCoderProgrammingProject.ConstructorDemo::m_s IL_0012: ldarg.0 IL_0013: ldc.r8 3.1415899999999999 IL_001c: stfld float64 NowCoderProgrammingProject.ConstructorDemo::m_d IL_0021: ldarg.0 IL_0022: ldc.i4 0xff IL_0027: stfld uint8 NowCoderProgrammingProject.ConstructorDemo::m_b IL_002c: ldarg.0 IL_002d: call instance void [mscorlib]System.Object::.ctor() IL_0032: nop IL_0033: nop IL_0034: ldarg.0 IL_0035: ldarg.1 IL_0036: stfld int32 NowCoderProgrammingProject.ConstructorDemo::m_x IL_003b: ldarg.0 IL_003c: ldarg.2 IL_003d: stfld string NowCoderProgrammingProject.ConstructorDemo::m_s IL_0042: nop IL_0043: ret } // end of method ConstructorDemo::.ctor
在看这个对应的IL代码发现其实和上面的几个构造函数有很多重复的代码,即初始化变量。如果在新增一个字段或者新增一个构造函数,就意味每个构造函数都会有对应重复的变量初始化,尤其是新增内联字段,代码会增加的更多。如果每个构造函数对字段值进行赋值操作,那么对应生成的IL代码会更多(读者可自行尝试)这就是代码的膨胀。
对应的解决方案就是创建单个构造器来执行这些公共初始化。然后让其它构造器显式调用这个公共初始化构造器。这样可以减少生成的代码。在C#中利用this关键字来显式调用另一个构造器:对上面的代码修改如下:
class ConstructorDemo2 { private int m_x; private string m_s; private double m_d; private byte m_b; public ConstructorDemo2() { m_x = 5; m_s = "hello world"; m_d = 3.14159; m_b = 0xff; } public ConstructorDemo2(int x) : this() { m_x = x; } public ConstructorDemo2(string s) : this() { m_s = s; m_d = 10; } public ConstructorDemo2(int x, string s) : this() { m_x = x; m_s = s; } }
对应的IL代码如下:
无参: .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { // 代码大小 54 (0x36) .maxstack 8 IL_0000: ldarg.0 IL_0001: call instance void [mscorlib]System.Object::.ctor() IL_0006: nop IL_0007: nop IL_0008: ldarg.0 IL_0009: ldc.i4.5 IL_000a: stfld int32 NowCoderProgrammingProject.ConstructorDemo2::m_x IL_000f: ldarg.0 IL_0010: ldstr "hello world" IL_0015: stfld string NowCoderProgrammingProject.ConstructorDemo2::m_s IL_001a: ldarg.0 IL_001b: ldc.r8 3.1415899999999999 IL_0024: stfld float64 NowCoderProgrammingProject.ConstructorDemo2::m_d IL_0029: ldarg.0 IL_002a: ldc.i4 0xff IL_002f: stfld uint8 NowCoderProgrammingProject.ConstructorDemo2::m_b IL_0034: nop IL_0035: ret } // end of method ConstructorDemo2::.ctor int类型构造参数: .method public hidebysig specialname rtspecialname instance void .ctor(int32 x) cil managed { // 代码大小 17 (0x11) .maxstack 8 IL_0000: ldarg.0 IL_0001: call instance void NowCoderProgrammingProject.ConstructorDemo2::.ctor() IL_0006: nop IL_0007: nop IL_0008: ldarg.0 IL_0009: ldarg.1 IL_000a: stfld int32 NowCoderProgrammingProject.ConstructorDemo2::m_x IL_000f: nop IL_0010: ret } // end of method ConstructorDemo2::.ctor string 类型参数: .method public hidebysig specialname rtspecialname instance void .ctor(string s) cil managed { // 代码大小 32 (0x20) .maxstack 8 IL_0000: ldarg.0 IL_0001: call instance void NowCoderProgrammingProject.ConstructorDemo2::.ctor() IL_0006: nop IL_0007: nop IL_0008: ldarg.0 IL_0009: ldarg.1 IL_000a: stfld string NowCoderProgrammingProject.ConstructorDemo2::m_s IL_000f: ldarg.0 IL_0010: ldc.r8 10. IL_0019: stfld float64 NowCoderProgrammingProject.ConstructorDemo2::m_d IL_001e: nop IL_001f: ret } // end of method ConstructorDemo2::.ctor string 和int类型参数 .method public hidebysig specialname rtspecialname instance void .ctor(int32 x, string s) cil managed { // 代码大小 24 (0x18) .maxstack 8 IL_0000: ldarg.0 IL_0001: call instance void NowCoderProgrammingProject.ConstructorDemo2::.ctor() IL_0006: nop IL_0007: nop IL_0008: ldarg.0 IL_0009: ldarg.1 IL_000a: stfld int32 NowCoderProgrammingProject.ConstructorDemo2::m_x IL_000f: ldarg.0 IL_0010: ldarg.2 IL_0011: stfld string NowCoderProgrammingProject.ConstructorDemo2::m_s IL_0016: nop IL_0017: ret } // end of method ConstructorDemo2::.ctor
读者可以对比一下编译生成的代码少了很多。这是很推荐的一种写法。
我在附上几幅对比图,会更加明了(左侧统一为ConstructorDemo源码,右侧统一为ConstructorDemo2):
无参构造函数IL代码对比图
int构造函数IL代码对比图
string构造函数IL代码对比图
int,string构造函数IL代码对比图
参考资料:
CLR VIA C#(第3版)
相关文章推荐
- C#软件反编译后代码错误的改错方法
- C#程序集系列01,用记事本编写C#,IL代码,用DOS命令编译程序集,运行程序
- 从IL代码来探讨C#中的接口方法、虚方法与抽象方法
- C#实现将记事本中的代码编译成可执行文件的方法
- 编写高质量代码改善C#程序的157个建议——建议145:避免过长的方法和过长的类
- c# il (中间代码)exe,dll(编译成的程序) 之间的转换
- CLR基础,CLR运行过程,使用dos命令创建、编译、运行C#文件,查看IL代码
- swig编译GDAL的C#库时遇到的代码安全问题及解决方法
- c# 反编译与混淆代码的加密方法
- 回复读者问题:为何C#中类方法的IL代码要放在类型表中?
- DLL反编译,DLL反编译成C#代码, 有些不良同学会用到哦!
- 编写高质量代码改善C#程序的157个建议——建议95:避免在构造方法中调用虚成员
- 菜鸟之[C#WinForm传值(方法二)(详细代码及注释)]
- 代码实现C#中最大化最小化按钮方法
- 防止JAVA代码被反编译的方法
- 解析方法体中的 IL 代码
- [译] 解析方法体中的 IL 代码
- FootStep 8、VC6下进行Symbian开发时,非代码问题引起的编译及链接出错的解决方法
- C# 写的 CPU 浮点运算测试工具.并且还有获取CPU种种数据的方法(图)(代码)
- C# 2.0:使用匿名方法、迭代程序和局部类来创建优雅的代码