C#成员初始化有点坑爹

简介:

C#成员的初始化顺序你真的非常清楚吗,我发现有点坑爹,坑到爹突然有点搞不清楚什么状况。下面咱们开始分析,先看3个简单类。

 

复制代码
    public abstract class Base
    {
        public Base()
        {
            SetValue();
        }
        public abstract void SetValue();
    }

    public class Sub : Base
    {
        public string value;
        public Sub()
        {
            value = "chentaihan";
        }

        public override void SetValue()
        {
            value = "陈太汉";
        }
    }

    public class Sub1 : Base
    {
        public string value = "chentaihan";

        public override void SetValue()
        {
            value = "陈太汉";
        }
    }
复制代码

 

如果执行下面这段代码会输出什么值呢,请不要往下看,先给出你自己的答案。

 

复制代码
 static class Program
    {
        static void Main(string[] args)
        {
            Sub sub = new Sub();
            Console.WriteLine(sub.value);
            Sub1 sub1 = new Sub1();
            Console.WriteLine(sub1.value);
            Console.Read();
        }
    }
复制代码

 

是的他很简单,但你确信你的答案就是对的吗?这么一个简单的问题我答错了,所以就有了这篇博客。 CLR VIA C#这本书告诉我们:成员在定义的时候初始化相当于在构造函数的最上面初始化,如果一个成员在定义的时候初始化,并在构造函数中赋值,那么在构造函数执行完成之后,该成员的值就是造函数中所赋的值,所以我得出的答案都是:chentaihan。但答案不是这样的。当运行结果出来时,我那个迷茫啊.....

 

先来说说我的简单分析:

1:进入子类构造函数

2Sub成员变量的内存被分配

3:调用父类构造函数

4:调用子类的方法SetValue(子类覆写了这个方法)value被赋值

5:正式执行子类构造函数,成员变量value再次被赋值

从上面5步我得出他们输出的结果一样,都是chentaihan。错在哪里呢?

于是我用Reflector查看了一下,得到的结果正如上面所说,他们的源码是一样的,如下所示。正如CLR VIA C#这本书说的那样,那为什么结果不一样呢,Reflector代码是一样的,执行的结果却不一样,怎么回事,怎么回事,那我只能说Reflector坑爹,它不能反映程序的真正执行逻辑,非要我用IL,我用的还不熟呢。

复制代码
   public class Sub : Base
    {
        public string value;
        public Sub()
        {
            value = "chentaihan";
        }

        public override void SetValue()
        {
            value = "陈太汉";
        }
    }
复制代码

 

神马情况,他们的IL代码是不一样的,如图所示

看了这个图,我们知道答案是chentaihan,陈太汉。谁能告诉我怎么调用父类的构造函数和给value赋值的顺序不一样啊。该用的工具都用了,我该怎么证明这个结果,于是开始单步调试,于是发现了一个每天都发现了的秘密:成员初始化在构造函数之前执行。难怪这本书上说成员在定义的时候初始化相当于在构造函数的最上面初始化,Reflector也证实了这个答案。但是又绕进另一个坑爹的问题:构造函数还没有调用,内存还没有分配,怎么给成员变量赋值啊?这不是问题,从上图可以看出成员变量的赋值只是在父类的构造函数之前调用,肯定也是在子类的成员变量分配空间之后为成员变量赋值。好的,最后我们得出的结论是: 

1:进入子类构造函数

2Sub成员变量的内存被分配

3Sub成员变量赋值

4:调用父类构造函数

5:调用子类的方法SetValue(子类覆写了这个方法)value被赋值

6:正式执行子类构造函数,成员变量value再次被赋值

同意以上观点的人请放过我,别吐槽,不同意的请留言

这样的解释答案就很合理,但同时也说明成员变量在定义的时候初始化和在构造函数中赋值的意义是不一样的,至少执行顺序不一样,产生的结果可能也不一样。

 

 

 

作者:陈太汉

博客:http://www.cnblogs.com/hlxs/

 



本文转自啊汉博客园博客,原文链接:http://www.cnblogs.com/hlxs/archive/2012/04/19/2456949.html

目录
相关文章
|
3月前
|
C# 开发者
C# 9.0中的模块初始化器:程序启动的新控制点
【1月更文挑战第14天】本文介绍了C# 9.0中引入的新特性——模块初始化器(Module initializers)。模块初始化器允许开发者在程序集加载时执行特定代码,为类型初始化提供了更细粒度的控制。文章详细阐述了模块初始化器的语法、用途以及与传统类型初始化器的区别,并通过示例代码展示了如何在实际项目中应用这一新特性。
|
1月前
|
自然语言处理 C# 数据安全/隐私保护
50.c#:string类初始化
50.c#:string类初始化
12 1
|
3月前
|
存储 C# 容器
掌握 C# 变量:在代码中声明、初始化和使用不同类型的综合指南
变量是用于存储数据值的容器。 在 C# 中,有不同类型的变量(用不同的关键字定义),例如: int - 存储整数(没有小数点的整数),如 123 或 -123 double - 存储浮点数,有小数点,如 19.99 或 -19.99 char - 存储单个字符,如 'a' 或 'B'。Char 值用单引号括起来 string - 存储文本,如 "Hello World"。String 值用双引号括起来 bool - 存储具有两个状态的值:true 或 false
37 2
|
9月前
|
C#
C#中声明、初始化和实例化
C#中声明、初始化和实例化