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

目录
相关文章
|
7月前
|
C# 开发者
C# 9.0中的模块初始化器:程序启动的新控制点
【1月更文挑战第14天】本文介绍了C# 9.0中引入的新特性——模块初始化器(Module initializers)。模块初始化器允许开发者在程序集加载时执行特定代码,为类型初始化提供了更细粒度的控制。文章详细阐述了模块初始化器的语法、用途以及与传统类型初始化器的区别,并通过示例代码展示了如何在实际项目中应用这一新特性。
|
7月前
|
Java C#
C# 面向对象编程解析:优势、类和对象、类成员详解
OOP代表面向对象编程。 过程式编程涉及编写执行数据操作的过程或方法,而面向对象编程涉及创建包含数据和方法的对象。 面向对象编程相对于过程式编程具有几个优势: OOP执行速度更快,更容易执行 OOP为程序提供了清晰的结构 OOP有助于保持C#代码DRY("不要重复自己"),并使代码更易于维护、修改和调试 OOP使得能够创建完全可重用的应用程序,编写更少的代码并减少开发时间 提示:"不要重复自己"(DRY)原则是有关减少代码重复的原则。应该提取出应用程序中常见的代码,并将其放置在单一位置并重复使用,而不是重复编写。
74 0
|
4月前
|
C#
一文搞懂C#中类成员的可访问性
一文搞懂C#中类成员的可访问性
55 5
|
API C#
C#反射与特性(三):反射类型的成员
C#反射与特性(三):反射类型的成员
279 0
|
6月前
|
开发框架 .NET 编译器
程序与技术分享:C#基础知识梳理系列三:C#类成员:常量、字段、属性
程序与技术分享:C#基础知识梳理系列三:C#类成员:常量、字段、属性
38 2
|
7月前
|
自然语言处理 C# 数据安全/隐私保护
50.c#:string类初始化
50.c#:string类初始化
255 1
|
7月前
|
C#
C#对象初始化器
C#对象初始化器
C#中声明、初始化和实例化
C#中声明、初始化和实例化
103 0
|
7月前
|
C# 开发者 索引
C# 11.0中的所需成员:强化接口与抽象类的约束
【1月更文挑战第24天】C# 11.0引入了所需成员(Required members)的概念,这一新特性允许在接口和抽象类中定义必须被实现的成员,包括方法、属性、索引器和事件。通过所需成员,C# 强化了对接口实现和抽象类继承的约束,提高了代码的一致性和可维护性。本文将详细探讨C# 11.0中所需成员的工作原理、使用场景及其对现有编程模式的影响。
|
7月前
|
C# 开发者 索引
C# 11.0中的静态抽象成员:接口中的新变革
【1月更文挑战第25天】C# 11.0引入了接口中的静态抽象成员,这一新特性为接口设计带来了更大的灵活性。静态抽象成员允许在接口中定义静态方法和属性,并要求实现类提供具体的实现。本文将详细探讨C# 11.0中静态抽象成员的工作原理、优势及其对现有编程模式的影响,旨在帮助读者更好地理解和应用这一新特性。