构造器


  构造器(构造函数)是将类型的实例初始化的特殊方法。构造器可分为实例构造器类型构造器,本节将详细介绍有关内容。

实例构造器

  顾名思义,实例构造器的作用就是对类型的实例进行初始化。如果类没有显示定义任何构造器,C#编译器会定义一个默认的无参构造器。相反,如果类中已经显示地定义了一个构造器,那么就不会再生成默认构造器了。定义实例构造器的语法这里就不再多做阐述了(该懂得要懂呀),下面通过一个简单的示例讲述实例构造器的执行原理。

public class Rapper
{
private string name;
private int age;
private bool real = true; public Rapper(string name,int age)
{
this.name = name;
this.age = age;
}
}

通过上述代码,我们创建了一个Rapper类,并定义了一个实例构造器,下面通过ildasm.exe工具查看构造器方法(.ctor)的IL代码。

.method public hidebysig specialname rtspecialname
instance void .ctor(string name,
int32 age) cil managed
{
// Code size 30 (0x1e)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldc.i4.1
IL_0002: stfld bool ConsoleApplication5.Rapper::real
IL_0007: ldarg.0
IL_0008: call instance void [mscorlib]System.Object::.ctor()
IL_000d: nop
IL_000e: nop
IL_000f: ldarg.0
IL_0010: ldarg.1
IL_0011: stfld string ConsoleApplication5.Rapper::name
IL_0016: ldarg.0
IL_0017: ldarg.2
IL_0018: stfld int32 ConsoleApplication5.Rapper::age
IL_001d: ret
} // end of method Rapper::.ctor

执行步骤:

  1. Rapper的构造器把值true存储到字段real
  2. 调用Object类的构造器
  3. 加载第一个参数存储到字段name
  4. 加载第二个参数存储到字段age

虽然我们在声明real字段时直接赋值为true,但是在编译时,编译器会将这种语法转换成构造器方法中的代码来进行初始化。

我们知道,一个类型可以定义多个构造器,每个构造器须有不同签名,将Rapper类稍加修改.

public class Rapper
{
private string name;
private int age;
private bool real = true;
private bool diss = true;
private bool forgetWords = true; public Rapper(string name, int age)
{
this.name = name;
this.age = age;
} public Rapper(string name)
{
this.name = name;
}
}

通过ildasm.exe工具查看两段构造器的IL代码,会发现在每个方法开始的位置都包含用于初始化real,diss,forgetWords的代码

为了减少生成的代码,可以利用this关键字显式调用另外一个构造器

public class Rapper
{
private string name;
private int age;
private bool real = true;
private bool diss = true;
private bool forgetWords = true; public Rapper(string name, int age) : this(name)
{
this.age = age;
} public Rapper(string name)
{
this.name = name;
}
}

到目前为止,我们讨论的都是引用类型的实例构造器,下面,再来看看值类型的实例构造器。这里只用一句话来概括:值类型不允许包含显式的无参数构造器,如果为值类型定义构造器,必须显示调用才会执行

类型构造器

  类型构造器也称静态构造函数,类型构造器的作用是设置类型的初始状态。类型默认没有定义类型构造器。如果定义,只能定义一个。类型构造器没有参数。

类型构造器的特点:

  1. 定义类型构造器类似于实例构造器,区别在于必须标记为static
  2. 类型构造器总是私有的,静态构造器不允许出现访问修饰符

类型构造器的执行原理:

  1. JIT编译在编译一个方法时,会查看代码中所有引用的类型
  2. 判断类型是否定义了类型构造器
  3. 针对当前的AppDomain,检查是否已经调用了该类型构造器
  4. 如果没有,JIT编译器会在生成的native代码中添加对类型构造器的调用

类型构造器中的代码只能访问静态字段,与实例构造器相同,在类中声明静态字段并直接赋值时,编译器会自动生成一个类型构造器,并在类型构造器中初始化该值。为上面的Rapper类添加静态字段hobby

private static string hobby = "rap";

查看类型构造器方法(.cctor)的IL代码。

.method private hidebysig specialname rtspecialname static
void .cctor() cil managed
{
// Code size 11 (0xb)
.maxstack 8
IL_0000: ldstr "rap"
IL_0005: stsfld string ConsoleApplication5.Rapper::hobby
IL_000a: ret
} // end of method Rapper::.cctor

操作符重载方法


  有的语言允许类型定义操作符来操作类型的实例。CLR对操作符一无所知,是编程语言定义了每个操作符的含义,以及调用这些操作符时生成的代码。向Rapper类添加如下代码:

    public static string operator +(Rapper rapperA, Rapper rapperB)
{
if (rapperA.name == "PGOne" || rapperB.name == "PGOne")
{
return "diss";
}
return "peace";
}

注意:

  1. CLR规范要求操作符重载方法必须是public和static方法
  2. 使用operator关键字告诉编译器,这是一个自定义操作符重载方法

修改Main方法,声明两个Rapper对象,并输出rapperA + rapperB的返回值。

class Program
{
static void Main(string[] args)
{
Rapper rapperA = new Rapper("PGOne");
Rapper rapperB = new Rapper("GAI");
Console.WriteLine(rapperA + rapperB); //diss
Console.ReadLine();
}
}

下面,使用ILDasm.exe工具查看编译器生成的IL代码。

.method private hidebysig static void  Main(string[] args) cil managed
{
.entrypoint
// Code size 43 (0x2b)
.maxstack 2
.locals init ([0] class ConsoleApplication5.Rapper rapperA,
[1] class ConsoleApplication5.Rapper rapperB)
IL_0000: nop
IL_0001: ldstr "PGOne"
IL_0006: newobj instance void ConsoleApplication5.Rapper::.ctor(string)
IL_000b: stloc.0
IL_000c: ldstr "GAI"
IL_0011: newobj instance void ConsoleApplication5.Rapper::.ctor(string)
IL_0016: stloc.1
IL_0017: ldloc.0
IL_0018: ldloc.1
IL_0019: call string ConsoleApplication5.Rapper::op_Addition(class ConsoleApplication5.Rapper,
class ConsoleApplication5.Rapper)
IL_001e: call void [mscorlib]System.Console::WriteLine(string)
IL_0023: nop
IL_0024: call string [mscorlib]System.Console::ReadLine()
IL_0029: pop
IL_002a: ret
} // end of method Program::Main

通过IL_0019一行,我们可以看到代码中出现+操作符时,实际调用的是op_Addition方法,再查看op_Addition方法的IL代码。

.method public hidebysig specialname static
string op_Addition(class ConsoleApplication5.Rapper rapperA,
class ConsoleApplication5.Rapper rapperB) cil managed
{
// Code size 61 (0x3d)
.maxstack 2
.locals init ([0] bool V_0,
[1] string V_1)
IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldfld string ConsoleApplication5.Rapper::name
IL_0007: ldstr "PGOne"
IL_000c: call bool [mscorlib]System.String::op_Equality(string,
string)
IL_0011: brtrue.s IL_0025
IL_0013: ldarg.1
IL_0014: ldfld string ConsoleApplication5.Rapper::name
IL_0019: ldstr "PGOne"
IL_001e: call bool [mscorlib]System.String::op_Equality(string,
string)
IL_0023: br.s IL_0026
IL_0025: ldc.i4.1
IL_0026: stloc.0
IL_0027: ldloc.0
IL_0028: brfalse.s IL_0033
IL_002a: nop
IL_002b: ldstr "diss"
IL_0030: stloc.1
IL_0031: br.s IL_003b
IL_0033: ldstr "peace"
IL_0038: stloc.1
IL_0039: br.s IL_003b
IL_003b: ldloc.1
IL_003c: ret
} // end of method Rapper::op_Addition

执行步骤:

  1. 编译器为op_Addition方法生成元数据方法定义项,并在定义项中设置了specialname标志,表明这是一个特殊方法。
  2. 编译器发现代码中出现+操作符时,会检查是否有一个操作数的类型定义了名为op_Addition的specialname方法,而且该方法的参数兼容于操作数的类型。
  3. 如果存在这样的方法,就生成调用它的代码。

转换操作符方法


  有时需要将对象从一种类型转换为另外一种全然不同的其他类型,此时便可以通过转换操作符实现自定义类型转换。同样的,CLR规范要求转换操作符重载方法必须是public和static的,并且要求参数类型和返回类型二者必有其一与定义转换方法的类型相同

  在C#中使用implicitexplicit关键字定义隐式/显示类型转换。在Implicit或explicit关键字后,要指定operator关键字告诉编译器该方法是一个转换操作符。在operator之后,指定目标类型,而在参数部分指定源类型。

依旧沿用上面的示例,为Rapper类添加Rap方法,并为其添加无参构造函数。

public void Rap()
{
Console.WriteLine("Rap");
} public Rapper()
{ }

新增Dancer类,添加Dance方法,使用implicit/explicit关键字定义隐式/显示类型转换。

public class Dancer
{
public void Dance()
{
Console.WriteLine("Breaking");
} public static implicit operator Rapper(Dancer dancer)
{
return new Rapper();
} public static explicit operator Dancer(Rapper rapper)
{
return new Dancer();
}
}

修改Main方法:

class Program
{
static void Main(string[] args)
{
Rapper rapperA = new Rapper();
Dancer dancerA = (Dancer)rapperA;
dancerA.Dance(); //Breaking Dancer dancerB = new Dancer();
Rapper rapperB = dancerB;
rapperB.Rap(); //Rap
Console.ReadLine();
}
}

最后,查看编译器生成的IL代码可以发现,将对象从一种类型转换为另一种类型的方法总是叫做op_Implicitop_Explicit

.method private hidebysig static void  Main(string[] args) cil managed
{
.entrypoint
// Code size 48 (0x30)
.maxstack 1
.locals init ([0] class ConsoleApplication5.Rapper rapperA,
[1] class ConsoleApplication5.Dancer dancerA,
[2] class ConsoleApplication5.Dancer dancerB,
[3] class ConsoleApplication5.Rapper rapperB)
IL_0000: nop
IL_0001: newobj instance void ConsoleApplication5.Rapper::.ctor()
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: call class ConsoleApplication5.Dancer ConsoleApplication5.Dancer::op_Explicit(class ConsoleApplication5.Rapper)
IL_000d: stloc.1
IL_000e: ldloc.1
IL_000f: callvirt instance void ConsoleApplication5.Dancer::Dance()
IL_0014: nop
IL_0015: newobj instance void ConsoleApplication5.Dancer::.ctor()
IL_001a: stloc.2
IL_001b: ldloc.2
IL_001c: call class ConsoleApplication5.Rapper ConsoleApplication5.Dancer::op_Implicit(class ConsoleApplication5.Dancer)
IL_0021: stloc.3
IL_0022: ldloc.3
IL_0023: callvirt instance void ConsoleApplication5.Rapper::Rap()
IL_0028: nop
IL_0029: call string [mscorlib]System.Console::ReadLine()
IL_002e: pop
IL_002f: ret
} // end of method Program::Main

扩展方法


  扩展方法已经在《从LINQ开始之LINQ to Objects(下)》一文中进行了详细介绍,本篇就不再重复了。

C#构造函数、操作符重载以及自定义类型转换的更多相关文章

  1. 不可或缺 Windows Native (24) - C++: 运算符重载, 自定义类型转换

    [源码下载] 不可或缺 Windows Native (24) - C++: 运算符重载, 自定义类型转换 作者:webabcd 介绍不可或缺 Windows Native 之 C++ 运算符重载 自 ...

  2. 21.C++- "++"操作符重载、隐式转换之explicit关键字、类的类型转换函数

    ++操作符重载 ++操作符分为前置++和后置++,比如: ++a;  a++; ++操作符可以进行全局函数或成员函数重载 重载前置++操作符不需要参数 重载后置++操作符需要一个int类型的占位参数 ...

  3. C++中的赋值操作符重载和拷贝构造函数

    1,关于赋值的疑问: 1,什么时候需要重载赋值操作符? 2,编译器是否提供默认的赋值操作符? 2,关于赋值的疑问: 1,编译器为每个类默认重载了赋值操作符: 1,意味着同类型的类对象可以相互赋值: 2 ...

  4. C++中复制构造函数与重载赋值操作符总结

    前言 这篇文章将对C++中复制构造函数和重载赋值操作符进行总结,包括以下内容: 1.复制构造函数和重载赋值操作符的定义: 2.复制构造函数和重载赋值操作符的调用时机: 3.复制构造函数和重载赋值操作符 ...

  5. C++中复制构造函数与重载赋值操作符

    我们都知道,在C++中建立一个类,这个类中肯定会包括构造函数.析构函数.复制构造函数和重载赋值操作:即使在你没有明确定义的情况下,编译器也会给你生成这样的四个函数.例如以下类:   class CTe ...

  6. 用ECMAScript4 ( ActionScript3) 实现Unity的热更新 -- 操作符重载和隐式类型转换

    C#中,某些类型会定义隐式类型转换和操作符重载.Unity中,有些对象也定义了隐式类型转换和操作符重载.典型情况有:UnityEngine.Object.UnityEngine.Object的销毁是调 ...

  7. 析构函数-复制构造函数-赋值操作符重载-默认构造函数<代码解析>

    通过下面primer中的一道习题,可以更深刻的了解,析构函数,复制构造函数,赋值操作符重载,默认构造函数的使用. 但是我的结果与primer习题解答里面的并不相同,可能是编译器不同的原因导致. // ...

  8. 操作符重载、继承(day08)

    二十 操作符重载 函数操作符"()" 功能:让对象当做函数来使用 注:对参数的个数.返回类型没有限制 eg: class A{...}; A a; //a.operator()(1 ...

  9. C++ operator overload -- 操作符重载

    C++ operator overload -- 操作符重载 2011-12-13 14:18:29 分类: C/C++ 操作符重载有两种方式,一是以成员函数方式重载,另一种是全局函数. 先看例子 # ...

随机推荐

  1. linux c++如何学习

    最近有人问我,linux c++工资这么高,怎么学习才能入门,只要有个项目经验能进入一个公司即可. 然后我就说了linux c++开发的整个流程,然后用项目作为目标进行学习,这其实是一种以目的为主导的 ...

  2. sping 框架学习之——初始篇

    sping框架学习: 1,什么是spring框架 spring是J2EE应用程序框架,是轻量级的IoC和AOP的容器框架,主要是针对javaBean的生命周期进行管理的轻量级容器,可以单独使用,也可以 ...

  3. goroutine 加 channel 代替递归调用,突破递归调用的层级限制

    package main import ( "fmt" "github.com/davecgh/go-spew/spew" "github.com/B ...

  4. 结合bootstrap fileinput插件和Bootstrap-table表格插件,实现文件上传、预览、提交的导入Excel数据操作流程

    1.bootstrap-fileinpu的简单介绍 在前面的随笔,我介绍了Bootstrap-table表格插件的具体项目应用过程,本篇随笔介绍另外一个Bootstrap FieInput插件的使用, ...

  5. JPA常用注解

    JPA全称Java Persistence API.JPA通过JDK 5.0注解或XML描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中.        JPA由EJB 3.0软件专家 ...

  6. 移动端https抓包那些事--进阶篇

    上一次和大家介绍了手机端https抓包的初级篇,即在手机未root或者未越狱的情况下如何抓取https流量,但是当时分析应用时会发现,好多应用的https的流量还是无法抓取到,这是为什么呢? 主要原因 ...

  7. selenium+python开发环境的搭建

    web 调试工具介绍和开发环境搭建 python与selenium开发环境搭建: 一.下载python软件:https://www.python.org/ 下载完后,进行安装,安装成功后,打开IDLE ...

  8. MyISAM 和InnoDB 区别

    MyISAM 和InnoDB 讲解 InnoDB和MyISAM是许多人在使用MySQL时最常用的两个表类型,这两个表类型各有优劣,视具体应用而定.基本的差别 为:MyISAM类型不支持事务处理等高级处 ...

  9. Akka(18): Stream:组合数据流,组件-Graph components

    akka-stream的数据流可以由一些组件组合而成.这些组件统称数据流图Graph,它描述了数据流向和处理环节.Source,Flow,Sink是最基础的Graph.用基础Graph又可以组合更复杂 ...

  10. 利用Arduino快速制作Teensy BadUSB, 攻击计算机

    BadUsb介绍 BadUSB是计算机安全领域的热门话题之一,该漏洞由Karsten Nohl和Jakob Lell共同发现,并在2014年的BlackHat安全大会上公布. 虽然已隔一两年,但还是有 ...