C#构造函数、操作符重载以及自定义类型转换

构造器


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

实例构造器

  顾名思义,实例构造器的作用就是对类型的实例进行初始化。如果类没有显示定义任何构造器,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  8IL_0000:  ldarg.0IL_0001:  ldc.i4.1IL_0002:  stfld      bool ConsoleApplication5.Rapper::realIL_0007:  ldarg.0IL_0008:  call       instance void [mscorlib]System.Object::.ctor()IL_000d:  nopIL_000e:  nopIL_000f:  ldarg.0IL_0010:  ldarg.1IL_0011:  stfld      string ConsoleApplication5.Rapper::nameIL_0016:  ldarg.0IL_0017:  ldarg.2IL_0018:  stfld      int32 ConsoleApplication5.Rapper::ageIL_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的代码

971601-20170905202431663-1004268377.jpg

为了减少生成的代码,可以利用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. 类型构造器总是私有的,静态构造器不允许出现访问修饰符

类型构造器的执行原理:

971601-20170905214050757-234950778.png

  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  8IL_0000:  ldstr      "rap"IL_0005:  stsfld     string ConsoleApplication5.Rapper::hobbyIL_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);   //dissConsole.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();    //BreakingDancer dancerB = new Dancer();Rapper rapperB = dancerB;rapperB.Rap();  //RapConsole.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(下)》一文中进行了详细介绍,本篇就不再重复了。

转载于:https://www.cnblogs.com/Answer-Geng/p/7481294.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/282419.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

「Dotnet 工具箱」 自动生成并绑定 Https 证书

这里是 Dotnet 工具箱,定期分享 Dotnet 有趣,有用的工具,不要忘记关注。介绍LettuceEncrypt 是一个使用 C# 开发的免费的工具,它和证书颁发机构 (CA)集成,比如 Lets Encrypt,它使用了…

1115: 零起点学算法22——华氏摄氏温度转换

1115: 零起点学算法22——华氏摄氏温度转换 Time Limit: 1 Sec Memory Limit: 64 MB 64bit IO Format: %lldSubmitted: 3522 Accepted: 1456[Submit][Status][Web Board]Description 输入一个华氏温度,根据公式C(5/9)(F-32)计算对应的摄氏温度。 Input 输入一个…

Navicat Premium 12 的安装破解

Navicat 这款软件可以说 是数据库可视化操作的神器, 有绿色的 (最原始版本, 好像现在已经不维护了) , 有金色的 (改良收费版 ) , 还有彩色的 (最新版) , 这里 , 推荐使用彩色版 (也就是截止目前最新的版本 12.0.27). 操作的话, 感觉相比于小绿和小金有很大改进 , 很棒 , 在此给…

Vuejs——组件——slot内容分发

2019独角兽企业重金招聘Python工程师标准>>> ①概述: 简单来说,假如父组件需要在子组件内放一些DOM,那么这些DOM是显示、不显示、在哪个地方显示、如何显示,就是slot分发负责的活。 ②默认情况下 父组件在子组件内套的…

turtle库基础练习

画一组同切圆 import turtleturtle.shape(turtle)turtle.circle(10) turtle.circle(20) turtle.circle(30) turtle.circle(40) turtle.circle(50) turtle.circle(60) turtle.circle(70) turtle.circle(80)turtle.hideturtle() turtle.done() 画一组同心圆 import turtleturtle.…

检查你的项目的引用包依赖关系

2019独角兽企业重金招聘Python工程师标准>>> 随着着开发的进展,你的项目越来越大,引用的第三方包越来越多,但如何查看都依赖了哪些包,甚至传递依赖又是怎样? 首先解决这个问题的前提,你的项目需要是maven项目,然后可以做如下设置: 选中项目,右键->ru…

git 项目操作

1 创建本地仓库,克隆远程项目代码到本地仓库2. 当我们在本地写了一些代码之后 , 查看本地仓库状态3. 提交改变到待提交区 git add .4. 提交代码到待推送区 git commit -m "新建项目kuman"5. 将本地代码推送到远程代码仓库 git push origin master:nanle 注: 将本地m…

(二)SpringBoot功能

web开发 spring boot web开发非常的简单,其中包括常用的json输出、filters、property、log等 json 接口开发 在以前的spring 开发的时候需要我们提供json接口的时候需要做那些配置呢 就这样我们会经常由于配置错误,导致406错误等等,spring bo…

----斐波那契数列---eval函数----类递归思想 栈 进出 思想

------------ 斐波那契 数列 ---------------【1&#xff0c;1,2,3,5,8,13,21,34&#xff0c;...】 1 列表方法实现 # l[1,1] # # # while len(l)<20: # # l.append(l[-1]l[-2]) # # print(l) # # while len(l)!4: # l.append(l[-1]l[-2]) # print(l) # 2 …

HybridTime - Accessible Global Consistency with High Clock Uncertainty

Amazon’s Dynamo [9] and Facebook’s Cassandra [13], relax the consistency model&#xff0c;and offer only eventual consistency. Others such as HBase [1] and BigTable [4] offer strong consistency only for operations touching a single partition, but not acr…

公司目前实行的git团队协作方案

1. git init 新建本地仓库2. git clone 项目地址 获取远程master代码3. 在本地master代码上进行开发, 并将修改提交到待推送区4. 开发完, 在本地master分支基础上创建ready分支5. 在本地ready分支上(本地测试分支), 拉取并合并远程nanle分支最新代码(远程测试分支)6. 将本地re…

bzoj3122 [Sdoi2013]随机数生成器(bsgs+扩欧+数列)

Description Input 输入含有多组数据&#xff0c;第一行一个正整数T&#xff0c;表示这个测试点内的数据组数。 接下来T行&#xff0c;每行有五个整数p&#xff0c;a&#xff0c;b&#xff0c;X1&#xff0c;t&#xff0c;表示一组数据。保证X1和t都是合法的页码。 注意&…

挑选合适自己的一门编程语言

2019独角兽企业重金招聘Python工程师标准>>> 导读想学编程的原因有很多&#xff0c;你也许是想要做一个程序&#xff0c;又或者你只是想投身于这个行业&#xff0c;所以&#xff0c;在选择你的第一门编程语言之前&#xff0c;问问你自己&#xff1a;你想要在哪里运行…

css 实现章节名称不换行,多余部分用 ... 代替

修改之前:修改之后: 代码: <p style "white-space: nowrap;text-overflow: ellipsis;overflow: hidden;"><? $d[name] ?></p> <i><? $d[pen_name] ?></i> <i><?phpforeach ($d[tags] as $t) {echo $t[tag_name];…

.NET 反向代理-YARP 部署Https(SSL)

相关文章&#xff1a;.NET 反向代理-YARP.NET 反向代理-YARP 根据域名转发分享一个基于Abp 和Yarp 开发的API网关项目使用 Yarp 做网关YARP&#xff08;Yet Another Reverse Proxy&#xff09;是使用 .NET 构建的高度可定制的反向代理C# 开源一个基于 yarp 的 API 网关 Demo&am…

shell脚本--cut命令

bash&shell系列文章&#xff1a;http://www.cnblogs.com/f-ck-need-u/p/7048359.html 1.1 选项说明 cut命令将行按指定的分隔符分割成多列&#xff0c;它的弱点在于不好处理多个分隔符重复的情况&#xff0c;因此经常结合tr的压缩功能。 -b&#xff1a;按字节筛选&#xff…

Windows 下 Redis 的下载和安装

一 安装redis 1. 下载redis https://github.com/MicrosoftArchive/redis/releases 注: 如果上面网址下载不了, 就到这里下载 https://download.csdn.net/download/m_nanle_xiaobudiu/104370342. 解压压缩文件夹3. 运行redis服务端到此 , redis已经可以正常使用了,但是为了方便…

什么是行内块元素?

2019独角兽企业重金招聘Python工程师标准>>> 我们都知道行内元素和块级元素&#xff0c;在实际开发中&#xff0c;经常会听到行内块元素&#xff0c;那么什么是行内块元素呢&#xff1f; 行内块元素实际就是把块元素以行的形式展现,保留了块元素可以设置的对应CSS属…

WPF-08 控件模板

模板是描述控件外观&#xff0c;WPF中每个控件都有一个默认的模板&#xff0c;你可以通过定义模板来重写控件可视化外观和行为&#xff0c;WPF中有两种常用的模板Control Template和Data TemplateControl Template控件模板定义了控件的可视化外观&#xff0c;所有的UI控件都有自…

Nginx +Tomcat 实现动静态分离(转)

Nginx Tomcat 实现动静态分离 动静态分离就是Nginx处理客户端的请求的静态页面(html页面)或者图片&#xff0c;Tomcat处理客户端请求的动态页面&#xff08;jsp页面&#xff09;&#xff0c;因为Nginx处理的静态页面的效率高于Tomcat。 一&#xff0e;Nginx简介&#xff1a; Ng…