《CLR via C#》读书笔记(5) -- 构造函数

    在读这一节的类容之前,我觉得构造函数我都用了至少几千上万遍了,还会有什么新鲜的东西吗?经过仔细的阅读这一节,发现这样一个小小的

主题,我不知道的并且对我们写程序至关重要的原来还有这么多...

    1. 并不是所有新对象的创建都需要用到构造函数
       
Object类型定义了一个实例方法:MemberwiseClone,该方法的功能是为新对象分配内存,然后将当前实例进行浅拷贝到新的内存块中。这个过程便不会调用

构造函数。

 

    2. 便捷的内联成员初始化

        首先贴一段我写了无数遍的代码:
       

class Person
{
        private string _name = "Jensen";

        private int _age = 26;

        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }

        public int Age
        {
            get { return _age; }
            set { _age = value; }
        }
}

 写过C++程序的都知道,在C++中是不能在定义类成员的时候同时对其进行初始化的。(一般在构造函数中进行初始化)

其实在C#中规则是一样的,只不过C#编译器比较友好。它允许内联对成员进行始化,然后编译的时候把初始化代码插入到所有构造函数中。(如果没有定义会生成一个默认

的无参构造函数)。

看一下IL代码证实一下:(我没有定义构造函数,因此编译器生成了一个默认的)

 

.method public hidebysig specialname rtspecialname 
        instance void  .ctor() cil managed
{
  // Code size       27 (0x1b)
  .maxstack  8
  IL_0000:  ldarg.0
  IL_0001:  ldstr      "Jensen"
  IL_0006:  stfld      string TestApp.Person::_name
  IL_000b:  ldarg.0
  IL_000c:  ldc.i4.s   26
  IL_000e:  stfld      int32 TestApp.Person::_age
  IL_0013:  ldarg.0
  IL_0014:  call       instance void [mscorlib]System.Object::.ctor()
  IL_0019:  nop
  IL_001a:  ret
// end of method Person::.ctor

可以很清楚的看到,IL_0001至IL_000e是编译器插入到构造函数中的成员初始化代码,成员的值便是我们通过内联的方法所设定的值。

我们可以尝试定义多个构造函数:

View Code
class Person
{
        private string _name = "Jensen";

        private int _age = 26;

        public Person()
        {

        }

        public Person(string name, int age)
        {
            _name = name;
            _age = age;
        }

        public Person(string name)
        {
            _name = name;
        }

        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }

        public int Age
        {
            get { return _age; }
            set { _age = value; }
        }
}

我们再看一下编译器为这三个构造函数生成的IL:

View Code
.method public hidebysig specialname rtspecialname 
        instance void  .ctor() cil managed
{
  // Code size       29 (0x1d)
  .maxstack  8
  IL_0000:  ldarg.0
  IL_0001:  ldstr      "Jensen"
  IL_0006:  stfld      string TestApp.Person::_name
  IL_000b:  ldarg.0
  IL_000c:  ldc.i4.s   26
  IL_000e:  stfld      int32 TestApp.Person::_age
  IL_0013:  ldarg.0
  IL_0014:  call       instance void [mscorlib]System.Object::.ctor()
  IL_0019:  nop
  IL_001a:  nop
  IL_001b:  nop
  IL_001c:  ret
// end of method Person::.ctor

.method public hidebysig specialname rtspecialname 
        instance void  .ctor(string name,
                             int32 age) cil managed
{
  // Code size       43 (0x2b)
  .maxstack  8
  IL_0000:  ldarg.0
  IL_0001:  ldstr      "Jensen"
  IL_0006:  stfld      string TestApp.Person::_name
  IL_000b:  ldarg.0
  IL_000c:  ldc.i4.s   26
  IL_000e:  stfld      int32 TestApp.Person::_age
  IL_0013:  ldarg.0
  IL_0014:  call       instance void [mscorlib]System.Object::.ctor()
  IL_0019:  nop
  IL_001a:  nop
  IL_001b:  ldarg.0
  IL_001c:  ldarg.1
  IL_001d:  stfld      string TestApp.Person::_name
  IL_0022:  ldarg.0
  IL_0023:  ldarg.2
  IL_0024:  stfld      int32 TestApp.Person::_age
  IL_0029:  nop
  IL_002a:  ret
// end of method Person::.ctor

.method public hidebysig specialname rtspecialname 
        instance void  .ctor(string name) cil managed
{
  // Code size       36 (0x24)
  .maxstack  8
  IL_0000:  ldarg.0
  IL_0001:  ldstr      "Jensen"
  IL_0006:  stfld      string TestApp.Person::_name
  IL_000b:  ldarg.0
  IL_000c:  ldc.i4.s   26
  IL_000e:  stfld      int32 TestApp.Person::_age
  IL_0013:  ldarg.0
  IL_0014:  call       instance void [mscorlib]System.Object::.ctor()
  IL_0019:  nop
  IL_001a:  nop
  IL_001b:  ldarg.0
  IL_001c:  ldarg.1
  IL_001d:  stfld      string TestApp.Person::_name
  IL_0022:  nop
  IL_0023:  ret
}

可以很明显的看到,每个构造函数里面都会有重复的代码。这至少会增加生成的程序集的大小。因此在这种情况下最好不要采用内联方式进行成员初始化,而是将

初始化代码放到其中一个构造函数中,然后在其他构造函数中调用该构造函数。这样能节省生成的IL的大小。如下:

View Code
class Person
{
        private string _name;

        private int _age;

        public Person()
        {
            _name = "Jensen";
            _age = 26;
        }

        public Person(string name, int age)
            : this()
        {
            _name = name;
            _age = age;
        }

        public Person(string name)
            : this()
        {
            _name = name;
        }

        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }

        public int Age
        {
            get { return _age; }
            set { _age = value; }
        }
}

 

注:其实如果我们仅仅是想将成员初始化为0或null,其实我们不需要做任何事情。因为在构造一个对象时,CLR会将其成员所占用的内存全初始化为0. 但是对于声明的局部变量

则不是如此,必须要显示的初始化为0,否则使用它时编译器会报错。

 

3.值类型构造函数

一直以来自己定义值类型机会比较少,一般都是直接定义类了。所以值类型的构造函数自然就不了解了。

值类型构造函数与引用类型的构造函数还是有些不同:

1. C#编译器不会为没有定义构造函数的值类型生成默认构造函数。由于这个原因,我们便无法使用便捷的内联方式初始化成员了。

2. C#编译器也不允许显示定义无参构造函数。

3. C#编译器限制在构造函数中必须将所有成员全部初始化。

 

4. 类型构造函数

其实所谓的类型构造函数就是我们已经用过很多次的静态构造函数。这个构造函数是CLR在第一次使用一个类型之前,在将类型加载进内存时会去调用的。

无论是值类型还是引用类型,都可以定义类型构造函数。如果没有定义,编译器都会为其生成一个默认的。其实说到这,我情不自禁的就想到了:

我们可以方便的使用内联方式来初始化值类型或引用类型中的静态成员了。

 

因为每个类型在一个AppDomain中只会被加载一次,直到AppDomain卸载后才会卸载。所在对类型的加载(静态构造函数的调用)是线程安全的。这里其实是一

个绝好的机会来初始化单例的Instance(注:这便是采用内联方式始化单例实例与在属性get方法中初始化实例的区别)。我突然觉得写代码的最高境界便是最大化的将要考虑的事情统统交给操作系统或运行时来做。

 

5. 实现类型构造函数 VS 不实现类型构造函数

其实从代码级别来看,这两个行为对于静态成员的初始化没有任何区别。但是从运行时的性能来讲,这两个行为就会有区别了。

关键在JIT在何处生成对静态构造函数的调用代码。参考以下代码:

class Program
{
        static void Main(string[] args)
        {
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine(Person._name);
            }
            Console.ReadKey();

        }
}

class Person
{
        public static string _name = "Jensen";
}

 

如果我们没有定义静态构造函数,JIT在编译Main函数的IL代码为本地代码时,会以for循环前面去生成代码调用静态构造函数。因为静态构造函数是

编译器所成生成,JIT知道将其放在for循环前面不会产生问题。这也算是JIT对代码的一个优化吧。

但是如果我们定义了静态构造函数,JIT在编译Main函数的IL代码时。便会将对静态构造函数的调用放在for循环的里面。原因是因为他不敢擅自挪动对静态构造器调用的

位置(在第一次访问类型之前),因为JIT不确定自定义的静态构造器的实现是否依赖于对其的调用位置。 这样导致的结果便是在for循环里面除第一次以外,每次都会多出

如下代码:

if(类型没有加载)

{

    加载类型

    调用静态构造器

}

这样其实也会影响运行性能。

posted @ 2012-12-02 21:01  self.refactoring  阅读(385)  评论(0编辑  收藏  举报