构造器


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

实例构造器

  顾名思义,实例构造器的作用就是对类型的实例进行初始化。如果类没有显示定义任何构造器,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. [补档][COGS 2434]暗之链锁

    [COGS 2434]暗之链锁 题目 传说中的暗之连锁被人们称为Dark.<!--more-->Dark是人类内心的黑暗的产物,古今中外的勇者们都试图打倒它.经过研究,你发现Dark呈现无 ...

  2. Alluxio 1.5集群搭建

    一.依赖文件安装 1.1 JDK 参见博文:http://www.cnblogs.com/liugh/p/6623530.html 二.文件准备 2.1 文件名称 alluxio-1.5.0-hado ...

  3. zabbix_server---微信报警

    (1)         企业应用-创建应用 1.除了对个人添加微信报警之外,还可以添加不同管理组,接受同一个应用推送的消息, 成员账号,组织部门ID,应用Agent ID,CorpID和Secret, ...

  4. 模拟实现C库的atoi、atof和itoa

    1.C函数atoi atoi (表示 alphanumeric to integer)是把字符串转换成整型数的一个函数.广泛的应用在计算机程序和办公软件中.atoi( ) 函数会扫描参数 nptr字符 ...

  5. hdu--1104--Remainder(简单的bfs)

    Remainder Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total S ...

  6. hdu--1711--kmp应用在整形数组--Number Sequence

    /* Name: hdu--1711--Number Sequence Author: shen_渊 Date: 16/04/17 19:58 Description: 第一次知道,KMP能用在整形数 ...

  7. NYOJ--STL--擅长排列的小明(强大的string :: iterator 和next_permutation)

    NYOJ--STL--擅长排列的小明 #include <iostream> #include <string> #include <algorithm> usin ...

  8. MySQL进程处于Waiting for table flush的分析

      最近遇到一个案例,很多查询被阻塞没有返回结果,使用show processlist查看,发现不少MySQL线程处于Waiting for table flush状态,查询语句一直被阻塞,只能通过K ...

  9. 集合set

    一.集合的作用 知识点回顾:可变类型是不可hash类型,不可变类型是可hash类型 作用:去重,关系运算 定义:可以包含多个元素,用逗号分割,集合的元素遵循三个原则: 1.每个元素必须是不可变类型(可 ...

  10. css3类选择器之结合元素选择器和多类选择器

    css3类选择器之结合元素选择器和多类选择器用法: <!DOCTYPE html> <html lang="en"> <head> <me ...