C#在类型实例化时都干了什么:从一道笔试题说开去

原文来自:http://www.cnblogs.com/instance/archive/2011/05/27/2059409.html

前一阵子我参加了一次笔试,其中有一道选择题让我印象深刻,是这样的:

实例化一个X类型对象时所执行的顺序:

A.调用X类型构造函数,调用X类型基类的构造函数,调用X类型内部字段的构造函数

B.调用X类型内部字段的构造函数,调用X类型基类的构造函数,调用X类型构造函数

C.调用X类型基类的构造函数,调用X类型构造函数,调用X类型内部字段的构造函数

D.调用X类型基类的构造函数,调用X类型内部字段的构造函数,调用X类型构造函数

我觉的这道题出得很没水平。在C++的世界里,我会毫不犹豫的选D。但是,由于C#引入了字段初始化器,所以选什么答案完全依赖于类具体是如何设计的。好吧,我们今天就来谈谈C#在类型实例化时都有哪些步骤。

首先我们都知道,对于类对象,在执行构造函数之前,我们需要使用关键字new来为新实例分配内存。new可以根据对象的类型来为其在堆上分配足够的空间,并且将这个对象的所有字段都设为默认值。也就是说,CLR会把该对象的所有引用类型字段设为null,而把值类型字段的所有底层二进制表示位设为0(本质上来说,不论是将值类型或引用类型字段初始化为“默认值”,其实都是把他们底层的数据位设为0)。这是任何类对象实例化的第一步。

我们暂且先不考虑对象有指定基类的情况,先看看下面的代码吧:

class MyClass{
static MyClass()
{
Console.WriteLine("静态构造函数被调用。");
}
private static Component staticField = new Component("静态字段被实例化。");
private Component instanceField = new Component("实例成员字段被实例化。");
public MyClass()
{
Console.WriteLine("对象构造函数被调用。");
}
}
//此类型用于作MyClass类的成员//此类型在实例化的时候可以再控制台输出自定义信息,以给出相关提示
class Component
{
public Component(String info)
{
Console.WriteLine(info);
}
}

class Program
{
static void Main(string[] args)
{
MyClass instance = new MyClass();
}
}

很显然,静态构造函数和静态字段的构造函数会首先被调用。因为CLR在使用任何类型实例之前一定会先装载该类型,也就需要调用静态构造函数并且初始化静态成员。但是,到底是先初始化静态成员呢,还是调用静态构造函数呢?答案是初始化静态成员,因为CLR必须保证在执行构造函数的方法体时,相关的成员变量应该都可以被安全地使用。同样的道理也适用于实例构造函数和字段,也就是说对象成员的实例化会先于成员构造函数被执行。顺便说一句,类定义直接初始化类\对象字段的功能是由类\对象字段初始化器完成的。以下是实例化MyClass对象时控制台的输出:

静态字段被实例化。
静态构造函数被调用。
实例成员字段被实例化。
对象构造函数被调用。

接下来,我们看看如果对象有指定的基类的情况:

class Base
{
static Base()
{
Console.WriteLine("基类静态构造函数被调用。");
}
 
private static Component baseStaticField = new Component("基类静态字段被实例化。");
private Component baseInstanceField = new Component("基类实例成员字段被实例化。");

public Base()
{
Console.WriteLine("基类构造函数被调用。");
}
}

//此类型用作派生类,同基类一样,它也包含静态构造函数,以及静态字段、实例成员字段各一个。
class Derived : Base
{
static Derived()
{
Console.WriteLine("派生类静态构造函数被调用。");
}

private static Component derivedStaticField = new Component("派生类静态字段被实例化。");
private Component derivedInstanceField = new Component("派生类实例成员字段被实例化。");

public Derived()
{
Console.WriteLine("派生类构造函数被调用。");
}
}

//此类型用于作为Base类和Derived类的成员
//此类型在实例化的时候可以在控制台输出自定义信息,以给出相关提示
class Component
{
public Component(String info)
{
Console.WriteLine(info);
}
}

//在主程序里实例化了一个子类对象
class Program
{
static void Main(string[] args)
{
Derived derivedObject = new Derived();
}
}

类似于上个例子里的MyClass,这里的子类Derived和基类Base都有静态构造函数,也包含静态和实例成员各一个。当实例化一个子类Derived对象的实例时,输出的结果可能并不容易想到:

派生类静态字段被实例化。
派生类静态构造函数被调用。
派生类实例成员字段被实例化。
基类静态字段被实例化。
基类静态构造函数被调用。
基类实例成员字段被实例化。
基类构造函数被调用。
派生类构造函数被调用。

从结果我们可以看出,派生类的静态字段初始化,静态构造函数调用,实例成员字段初始化都会先于基类的任何初始化动作被执行。对于派生类静态部分先被构造这一点比较容易理解,因为毕竟在CLR装载派生类Derived之前,基类Base还未被使用过,也就不会先被装载。

但是,为什么派生类的实例成员字段会在基类被构造之前被初始化呢?答案和虚函数有关。试想有这么一个基类,它在构造函数中调用了一个虚方法。然后又有这么一个派生类,它重写了基类的那个虚方法,并且在这个虚方法中访问了它自己的一个实例成员字段。这一切都是完全合法的(至少在C#的世界里是这样的),对吧?在实例化一个派生类对象的过程中,其基类的构造函数会被调用,接着那个虚方法也会被调用,再接着派生类的实例成员字段会被访问。所以此时此刻,这个类的实例成员字段必须是已被准备好了的!因此,派生类的实例成员字段必须先于基类部分被构造。

好了,再回到我们的例子。剩下的部分很容易理解:基类按照我们预想的方式被生成,然后派生类的构造函数被调用。至此,一个派生类的对象就被实例化了。

顺便说一句,关于类字段初始化器,或对象字段初始化器,他们初始化成员字段的顺序是成员在类定义中出现的先后顺序。再顺便说一句,如果程序的逻辑依赖于成员在类定义中出现的顺序则是不好的设计,这可能会大大降低您代码的易读性。

现在当我们再回过头看文章开头的题目时,一切都明朗了——根本就没有一个正确答案!因为如果X类型有对象字段初始化器,且其构造函数内没有初始化任何实例字段的话,答案应该选B。如果X类型没有对象字段初始化器,且其构造函数内初始化了实例字段的话,答案选C。如果X类型没有对象字段初始化器,且其构造函数内没有初始化任何实例字段的话,答案选D。再其他的情况,则没有答案可选了。

转载于:https://www.cnblogs.com/Vagrant-Wind/articles/2059789.html

C#在类型实例化时都干了什么:从一道笔试题说开去相关推荐

  1. 90%的人都不会做的一道笔试题

    关注"Java后端技术全栈" 回复"面试"获取全套大厂面试资料 数组操作的题目,有的确实比较容易,但并非每个问题都是如此.今天就来看道90%的人都不会做的笔试题 ...

  2. noi linux 试题解密,关于NOIP丨你想知道的都在这(附NOI2019笔试题库)

    提到自主招生,就不得不从最重要的比赛之一--NOIP说起. (ps:目前NOIP已改为CSP等级认证,时间.试题范围与NOIP暂无太大区别) 在NOIP(省级赛)取得省级一.二等奖在高考自主招生都会被 ...

  3. 转载:一篇文章看明白 Android 系统启动时都干了什么

    最近在看<深入理解Android内核设计思想>,老实说,作为教材,这本书实在写的不好.本来内核的东西就比应用层繁杂,需要有个条绳子牵着,有个框子框着才不好跟丢.这个书老是在章节开头抛出一些 ...

  4. 2022阿里、腾讯、字节都在问的SQL数据库笔试题及答案都给你整理好啦

    2021到了最后一个月份,年后肯定有蛮多小伙伴需要跳槽换工作,但对于年限稍短的软件测试工程师,难免会需要进行笔试,而在笔试中,基本都会碰到一道关于数据库的大题,今天这篇文章呢,就收录了下最近学员反馈上 ...

  5. new 实例化对象是啥意思_前端经典面试题解密:JS的new关键字都干了什么?

    写在前面: new关键字在实例化获取对象时都做了什么?是一道经常出现在前端面试时的问题.如果只是简单的了解new关键字是实例化构造函数获取对象,是万万不能够的.更深入的层级发生了什么呢?同时面试官想从 ...

  6. C# 类型实例化的语法糖--unity下诡异结果

    类型实例化语法糖就是如下的用法: public class Abc {public int ID { get; set; }public string Name { get; set; }public ...

  7. CPU 空闲时在干嘛?

    有趣! CPU 空闲时在干嘛? 人在空闲时会发呆会无聊,计算机呢? 假设你正在用计算机浏览网页,当网页加载完成后你开始阅读,此时你没有移动鼠标,没有敲击键盘,也没有网络通信,那么你的计算机此时在干嘛? ...

  8. 【Kotlin】函数类型 ( 函数类型 | 带参数名称的参数列表 | 可空函数类型 | 复杂函数类型 | 带接收者函数类型 | 函数类型别名 | 函数类型实例化 | 函数调用 )

    文章目录 I . 函数类型 II . 带参数名的参数列表 III . 可空函数类型 IV . 复杂函数类型解读 V . 函数类型别名 VI . 带 接收者类型 的函数类型 VII . 函数类型实例化 ...

  9. 面试华为测试岗后感想,真的很后悔这5年一直都干的是基础测试....

    前两天,我的一个朋友去大厂面试,跟我聊天时说:输的很彻底- 我问她:什么情况? 她说:很后悔这5年来一直都干的是功能测试- 相信许多测试人也跟我朋友一样,从事了软件测试很多年,却依然只会基础的功能测试 ...

最新文章

  1. 猜测:引力与空间映射
  2. 数据结构:单向链表的反转
  3. Java代码注释规约
  4. 彼聆智能语音机器人_电销行业的人工智能:智能语音电话机器人
  5. 独立软件测试团队在敏捷开发中的几个特别实践
  6. 云计算示范项目_“云计算和大数据”重点专项2018年度项目申报指南
  7. VS2015彻底卸载干净
  8. nios 双核 烧录_Nios程序烧写到EPCS方法
  9. 中国域名8大玩家传奇故事
  10. input发送a.jax_Java EE 7和JAX-RS 2.0
  11. 2019年的咖啡大战,从杭州开打?
  12. CWND和HWND之间的关系和转换 和获取方法
  13. 那些35岁的程序员哪里去了?
  14. 并发编程四:深入理解java线程
  15. 凤凰系统虚拟机装卡_虚拟机安装凤凰系统(PhoenixOS)教程
  16. Ubuntu_XMMS播放器使用
  17. html英文怎么读,tail是什么意思英语,tail怎么读啊!
  18. 服务器安装红旗linux,在服务器redflag 6.0中怎么安装红旗Linux 桌面版
  19. 中软融鑫 java笔试题_中软融鑫2014校招java开发工程师笔试题
  20. 小米手环导出心率_小米手环测量心率功能该怎么使用?

热门文章

  1. new Class() 与 Class.newInstance()
  2. Centos6.4下zabbix的安装配置
  3. 高级应用-路由协议配置
  4. bash 脚本编写_如何在Bash中编写循环
  5. 30个Linux安装案例,Make和Makefile说明,Linux工具,容器安全性,DevOps技巧等
  6. sysadmin默认密码_Sysadmin指南,开源电子邮件客户端,macOS应用程序,SELinux,Firefox扩展等...
  7. openstreetmap_OpenStreetMap参加奥运会,SourceForge改造等
  8. 优必选能开放软硬件源代码吗_四个项目供父母教孩子如何使用开放式硬件和电子产品...
  9. 实战 | 后端日志的前世今生
  10. jQuery (二)