C# 小测试(一):类成员初始化与构造函数执行的顺序

简介:

看看下面这段代码,你觉得它会输出什么呢?

    class Foo
    {
        public Foo(string s)
        {
            Console.WriteLine("Foo constructor: {0}", s);
        }
 
        public void Bar(){}
    }
 
    class Base
    {
        readonly Foo baseFoo = new Foo("Base initializer");
        public Base()
        {
            Console.WriteLine("Base constructor");
        }
    }
 
    class Derived : Base
    {
        readonly Foo derivedFoo = new Foo("Derived initializer.");
        public Derived()
        {
            Console.WriteLine("Derived constructor");
        }
    }
 
    class Program
    {
        static void Main(string[] args)
        {
            new Derived();
        }
    }

先猜一下吧,似乎应该是“Base initializer, Base constructor, Derived initializer, Derived constructor”。

事实上,应当是先执行类成员的初始化,顺序是从derived到base,然后是两个构造函数,顺序是从base从derived。

这种方式是很有意义的,在类继承体系中层次较深的类(离System.Object较远)将依赖于较浅的类(离System.Object较近)。但是很多人会相信调用的顺序应当等价于下面的伪代码:

// 期望的顺序
BaseConstructor()
{
    ObjectConstructor();
    baseFoo = new Foo("Base initializer");
    Console.WriteLine("Base constructor");
}
DerivedConstructor()
{
    BaseConstructor();
    derivedFoo = new Foo("Derived initializer");
    Console.WriteLine("Derived constructor");
}

而实际情况则是:

 // 实际的顺序
BaseConstructor()
{
    baseFoo = new Foo("Base initializer");
    ObjectConstructor();
    Console.WriteLine("Base constructor");
}
DerivedConstructor()
{
    derivedFoo = new Foo("Derived initializer");
    BaseConstructor();
    Console.WriteLine("Derived constructor");
} 

那么,这样处理是为什么呢?

...

...

...

我们来看一下,如果代码按期望的顺序(第一段伪代码)执行,会产生什么问题:

class Base
{
    public Base()
    {
        Console.WriteLine("Base constructor");
        if (this is Derived) (this as Derived).DoIt();
        // 如果是在创建Derived类的实例,就会遭遇null。
        Blah();
        // 如果是在创建MoreDerived类的实例,就会遭遇null。
    }
 
    public virtual void Blah() { }
}
 
class Derived : Base
{
    readonly Foo derivedFoo = new Foo("Derived initializer");
    public DoIt()
    {
        derivedFoo.Bar();
    }
}
 
class MoreDerived : Derived
{
    public override void Blah() { DoIt(); }
} 

看Base类的构造函数,如果按期望的顺序执行,那么在Base方法执行时,Derived类的实例成员并没有得到初始化,此时就会有NullReference异常了。

而按照实际执行的顺序,所有的实例成员都能确保被完整地初始化:)

当然了,如果readonly字段是在构造函数中进行的,那么上面的确保机制就不复存在了。

参考:

Why Do Initializers Run In The Opposite Order As Constructors? Part One 

Why Do Initializers Run In The Opposite Order As Constructors? Part Two


本文转自一个程序员的自省博客园博客,原文链接:http://www.cnblogs.com/anderslly/archive/2008/07/12/Why-Do-Initializers-Run-In-The-Opposite-Order-As-Constructors.html,如需转载请自行联系原作者。

目录
相关文章
|
1月前
|
开发框架 .NET C#
C#|.net core 基础 - 删除字符串最后一个字符的七大类N种实现方式
【10月更文挑战第9天】在 C#/.NET Core 中,有多种方法可以删除字符串的最后一个字符,包括使用 `Substring` 方法、`Remove` 方法、`ToCharArray` 与 `Array.Copy`、`StringBuilder`、正则表达式、循环遍历字符数组以及使用 LINQ 的 `SkipLast` 方法。
|
2月前
|
存储 C# 索引
C# 一分钟浅谈:数组与集合类的基本操作
【9月更文挑战第1天】本文详细介绍了C#中数组和集合类的基本操作,包括创建、访问、遍历及常见问题的解决方法。数组适用于固定长度的数据存储,而集合类如`List<T>`则提供了动态扩展的能力。文章通过示例代码展示了如何处理索引越界、数组长度不可变及集合容量不足等问题,并提供了解决方案。掌握这些基础知识可使程序更加高效和清晰。
81 2
|
27天前
|
测试技术 C# 数据库
C# 单元测试框架 NUnit 一分钟浅谈
【10月更文挑战第17天】单元测试是软件开发中重要的质量保证手段,NUnit 是一个广泛使用的 .NET 单元测试框架。本文从基础到进阶介绍了 NUnit 的使用方法,包括安装、基本用法、参数化测试、异步测试等,并探讨了常见问题和易错点,旨在帮助开发者有效利用单元测试提高代码质量和开发效率。
137 64
|
23天前
|
缓存 监控 数据挖掘
C# 一分钟浅谈:性能测试与压力测试
【10月更文挑战第20天】本文介绍了性能测试和压力测试的基础概念、目的、方法及常见问题与解决策略。性能测试关注系统在正常条件下的响应时间和资源利用率,而压力测试则在超出正常条件的情况下测试系统的极限和潜在瓶颈。文章通过具体的C#代码示例,详细探讨了忽视预热阶段、不合理测试数据和缺乏详细监控等常见问题及其解决方案,并提供了如何避免这些问题的建议。
48 7
|
25天前
|
Kubernetes 测试技术 持续交付
C# 一分钟浅谈:集成测试与系统测试
【10月更文挑战第19天】本文详细介绍了集成测试和系统测试的概念、目的及其在软件开发中的重要性。通过分析常见问题和易错点,结合代码示例,探讨了如何通过代码规范、自动化测试和持续集成等方法提高测试效果,确保软件质量和可靠性。
48 1
|
26天前
|
测试技术 C# 数据库
C# 一分钟浅谈:测试驱动开发 (TDD) 实践
【10月更文挑战第18天】测试驱动开发(TDD)是一种软件开发方法论,强调先编写测试代码再编写功能代码,以确保代码质量和可维护性。本文从 TDD 的基本概念入手,详细介绍了其核心步骤——编写测试、运行测试并失败、编写代码使测试通过,以及“红绿重构”循环。文章还探讨了 TDD 的优势,包括提高代码质量、促进设计思考、减少调试时间和文档化。此外,文中分析了常见问题及解决方案,如测试覆盖率不足、测试代码过于复杂、忽视重构和测试依赖过多,并通过一个简单的计算器类的代码案例,展示了 TDD 的实际应用过程。
34 1
|
2月前
|
测试技术 C# 开发工具
C# 标准性能测试
本文介绍如何使用 BenchmarkDotNet 工具进行性能测试。通过 GitHub 代码示例展示如何安装并应用此工具,对函数或静态方法进行精确的性能分析。首先需通过 NuGet 安装 BenchmarkDotNet,然后在测试函数上添加 `[Benchmark]` 特性,并使用 `BenchmarkRunner.Run&lt;&gt;();` 进行测试。测试过程中包括 Pilot、Warmup 和 Target 等阶段,并提供 Mean、Error 和 StdDev 等统计信息。
54 3
C# 标准性能测试
|
1月前
|
Java 程序员 C#
【类的应用】C#应用之派生类构造方法给基类构造方法传参赋值
【类的应用】C#应用之派生类构造方法给基类构造方法传参赋值
13 0
|
2月前
|
C# 数据安全/隐私保护
C# 一分钟浅谈:类与对象的概念理解
【9月更文挑战第2天】本文从零开始详细介绍了C#中的类与对象概念。类作为一种自定义数据类型,定义了对象的属性和方法;对象则是类的实例,拥有独立的状态。通过具体代码示例,如定义 `Person` 类及其实例化过程,帮助读者更好地理解和应用这两个核心概念。此外,还总结了常见的问题及解决方法,为编写高质量的面向对象程序奠定基础。
27 2
|
18天前
|
C# 开发者
C# 一分钟浅谈:Code Contracts 与契约编程
【10月更文挑战第26天】本文介绍了 C# 中的 Code Contracts,这是一个强大的工具,用于通过契约编程增强代码的健壮性和可维护性。文章从基本概念入手,详细讲解了前置条件、后置条件和对象不变量的使用方法,并通过具体代码示例进行了说明。同时,文章还探讨了常见的问题和易错点,如忘记启用静态检查、过度依赖契约和性能影响,并提供了相应的解决建议。希望读者能通过本文更好地理解和应用 Code Contracts。
30 3
下一篇
无影云桌面