实例构造与引用类型

之前的章节其实已经写过了引用类型的构造过程:

首先当然是,在堆中,为引用类型的实例对象分配内存,然后初始化对象的附加字段(即类型对象指针和同步块索引)。

这个时候为对象分配的内存都是直接被置为0的,所以如果所用到的构造器中没有对对象中的一些字段做处理,那么这些字段的初始值都应该为0或者null。

如果一个类,没有构造函数,那么这个类构造的时候就会定义一个默认无参构造器,它里面就简单调用基类的无参构造器。

极少数的情况下,会有不实用实例构造器就能创建类型的实例的情况,比如MemberwiseClone方法(深复制)和反序列化对象时。(反序列化会调用GetUninitializedObject或GetSafeUninitializedObject方法为对象分配内存,还是后面讲吧。)

还记得上一章节写的,内联初始化可能会导致性能问题吧:

因为每一次内联初始化实际上都会将这些初始化字段的操作,嵌入构造函数的代码中(注意会先进行这些操作,再进行真正的构造函数的操作)。如果只有一个构造器函数,那么不会有什么影响。然而如果有多个构造器函数,那么这几个构造器函数里都会插入这段初始化字段的代码。

所以当存在多个构造器参数,而代码里又有一大堆内联初始化,那么实际生成的代码中就会有大量的冗余代码。可以用以下方法解决:

  public class Troy{
//不进行内联初始化
int _a;
int _b;
public Troy() {
//将初始化过程放在某一构造参数内,一般就是无参构造函数中
this._a = ;
this._b = ;
}
public Troy(int i):this() //在其它构造函数时,调用this()
{ }
public Troy(int i,int j) : this()
{ }
}

实例构造与值类型

值类型其实不需要定义构造器,C#编译器也根本就不会为值类型内联嵌入默认的无参构造器。

只有嵌套在引用类型的值类型才会被初始化为0或null,如果是基于栈的值类型,那么在读取之前,被要求强制初始化,否则报错。

值类型的实例构造器只有显式调用才有用,否则其字段都会被初始化为0或null。(也就是说,即时这个struct有无参构造函数,只要你没有显示调用,那么就不会自动调用无参构造函数。实际上C#编译器也不允许你在结构体里写一个无参构造函数,毕竟这个点来说,太容易误会了)。

由于C#不允许值类型定义无参构造函数,所以值类型同样不准内联参数化。(静态字段可以内联初始化,因为是在类型对象里面,而不是实例对象)

且值类型的任何构造函数在初始化的时候,必须对值类型的所有字段都赋值。

对于这么麻烦的设定,当然也有解决的办法:

    public struct Troy {
public int a;
public int b;
public Troy(int i) {
this = new Troy();//先初始化所有的字段都为0或null
//初始化自己想玩的字段
a = i;
}
}

(我不得不吐槽,我刚刚用VS自己写了个值参数初始化的小例子,然后被360给当做病毒删掉了。)

关于类型构造器

首先要了解到,类型构造器实际上就是构造CLR分配内存中的类型对象初始化时用的。

类型构造器是不允许有参数的,当然也就只能定义一个类型构造函数。

实际上类型构造器必须是私有的,甚至不允许显式写上private修饰符,这样做正是为了防止开发人员调用。它的调用总是由CLR负责的。

简单样例:

class Program
{
static void Main(string[] args)
{
Troy obj = new Troy();
}
}
public class Troy {
static Troy() {
Console.WriteLine("我就问你6不6?");
}
}

构造过程:

JIT编译器编译一个方法时,会查看代码引用了哪些类型。任何一个类型定义了类型构造器,那么JIT编译器就会检查针对当前AppDomain是否执行了这个类型构造器。是就不调用,否就调用。因为CLR希望在每个AppDomain中一个类型构造器只执行一次,所以为了不使多个线程同时调用类型构造器,在第一个调用类型构造器的线程调用时,会获取一个互斥线程同步锁。这样一来,就只有一个线程可以调用了,后面的线程要用的时候发现已经调用过了,就不会再调用类型构造器了。(因为类型构造器线程安全,所以很适合在里面初始化任何单例对象。)

虽然可以在值类型中定义类型构造函数,然而实际上因为值类型根本就不会在堆中有类型对象,所以自然里面的代码都不会被调用。

关于操作符重载

实际上CLR对操作符重载一无所知,因为这是编程语言的语法。

当C#这种语言写的操作符重载语句被编译成IL代码时,其实已经变成了一个带有specialname标志的函数。

当编译器看到有+这种操作符时,就会看几个操作数的类型中是否有定义了名为op_Addition这个函数(被编译后的真正的函数名),而且该方法参数兼容于操作数的类型。

所以操作符重载函数中,一定要有一个参数的类型与定于这个重载方法的类型相同:

  public class Troy {
public static int operator +(Troy a, Troy b) {
return ;
}
}

关于转换操作符方法

  class Program
{
static void Main(string[] args)
{
Troy obj = ;//隐式转换成功
string a = obj;//由于是显示转换重载,所以这种写法会编译不过
string a =(String)obj;//显示转换成功
}
}
public class Troy {
// 隐式转换操作符implicit重载
public static implicit operator Troy(Int32 num) {
return new Troy();
}
// 显式转换操作符explicit重载
public static explicit operator String(Troy troy)
{
return "怎么转都是我";
}
}

和一般的+-这种操作符重载一样,实际上生成的IL代码中,换了一个名字,前缀加上了op_。

当C#编译器检测到代码中一个对象期望得到另一个类型不同的对象时,就回去找这两个类型中是否定义了隐式转换的op_Implicit方法,是就转。显示类似。

可以参考Decimal类的定义去理解。

关于扩展方法

先说一下我自己的认识吧,其实我不建议使用这个东西。

因为写得不规范的扩展方法,会增加了代码的阅读难度,增加维护成本。(我真的很确定有的人会把这个东西写得到处都是)

这个东西之前在写《重构》的学习笔记中提到过,主要用于解决别人封装的类库,没法增加自己想要的函数。

第19点.不完美的类库讲到的

简单来讲,还是慎用,自己写的类就别用扩展方法。

另外扩展方法必须是顶级静态类中定义的静态方法,如果是嵌套类中的话,编译会出错。

实际上扩展方法在C#编译器编译过后也只是个一般的静态对象里的静态函数,只不过加了个[Extension]的特性。然而实际上这个ExtensionAttribute特性还不能在代码中用,都是C#编译器去自动生成的。

关于分部方法

分部方法和分部类很像,不过是方法前面加上partial修饰符。

这样的话,如果其它分部类实现了这个方法,那么就会加上这个方法,如果没有实现,那么这条代码在编译的时候就会被忽略。

但是分部方法只能在分部类和结构中用,且返回类型总是void,任何参数都不能用out来修饰。之所以会这样限制,是因为方法在运行时可能就并不存在,所以也就不会有返回。

分部方法总是private的,但是C#编译器禁止将private修饰符显式写在分部方法前面。(和类型构造器在这个点上类似)

【C#进阶系列】08 方法的更多相关文章

  1. 【 D3.js 进阶系列 — 1.2 】 读取 CSV 文件时乱码的解决方法

    在 D3 中使用 d3.csv 读取 CSV 文件时,有时会出现乱码问题. 怎么解决呢? 1. 乱码问题 使用 d3.csv 读取 xxx.csv 文件时.假设 xxx.csv 文件使用的是 UTF- ...

  2. C#进阶系列 ---- 《CLR via C#》

      [C#进阶系列]30 学习总结 [C#进阶系列]29 混合线程同步构造 [C#进阶系列]28 基元线程同步构造 [C#进阶系列]27 I/O限制的异步操作 [C#进阶系列]26 计算限制的异步操作 ...

  3. C#进阶系列——WebApi 接口返回值不困惑:返回值类型详解

    前言:已经有一个月没写点什么了,感觉心里空落落的.今天再来篇干货,想要学习Webapi的园友们速速动起来,跟着博主一起来学习吧.之前分享过一篇 C#进阶系列——WebApi接口传参不再困惑:传参详解  ...

  4. C#进阶系列——WebApi 接口参数不再困惑:传参详解

    前言:还记得刚使用WebApi那会儿,被它的传参机制折腾了好久,查阅了半天资料.如今,使用WebApi也有段时间了,今天就记录下API接口传参的一些方式方法,算是一个笔记,也希望能帮初学者少走弯路.本 ...

  5. C#进阶系列——WebApi 接口测试工具:WebApiTestClient

    前言:这两天在整WebApi的服务,由于调用方是Android客户端,Android开发人员也不懂C#语法,API里面的接口也不能直接给他们看,没办法,只有整个详细一点的文档呗.由于接口个数有点多,每 ...

  6. C#进阶系列——WebApi 跨域问题解决方案:CORS

    前言:上篇总结了下WebApi的接口测试工具的使用,这篇接着来看看WebAPI的另一个常见问题:跨域问题.本篇主要从实例的角度分享下CORS解决跨域问题一些细节. WebApi系列文章 C#进阶系列— ...

  7. C#进阶系列——WebApi 身份认证解决方案:Basic基础认证

    前言:最近,讨论到数据库安全的问题,于是就引出了WebApi服务没有加任何验证的问题.也就是说,任何人只要知道了接口的url,都能够模拟http请求去访问我们的服务接口,从而去增删改查数据库,这后果想 ...

  8. C#进阶系列——WebApi 异常处理解决方案

    前言:上篇C#进阶系列——WebApi接口传参不再困惑:传参详解介绍了WebApi参数的传递,这篇来看看WebApi里面异常的处理.关于异常处理,作为程序员的我们肯定不陌生,记得在介绍 AOP 的时候 ...

  9. C#进阶系列——WebApi 路由机制剖析:你准备好了吗?

    前言:从MVC到WebApi,路由机制一直是伴随着这些技术的一个重要组成部分. 它可以很简单:如果你仅仅只需要会用一些简单的路由,如/Home/Index,那么你只需要配置一个默认路由就能简单搞定: ...

  10. C#进阶系列——一步一步封装自己的HtmlHelper组件:BootstrapHelper(三:附源码)

    前言:之前的两篇封装了一些基础的表单组件,这篇继续来封装几个基于bootstrap的其他组件.和上篇不同的是,这篇的有几个组件需要某些js文件的支持. 本文原创地址:http://www.cnblog ...

随机推荐

  1. Three levels at which any machine carrying out an Information-Processing task must be understood

    1. Computational theory What is the goal of computation, why is it appropriate, and what is the logi ...

  2. 2. MongoDB基本操作 —— 用Mongo.exe操作数据库增删改查

    一.开篇 传统的关系数据库一般由数据库(database).表(table).记录(record)三个层次概念组成,MongoDB是由数据库(database).集合(collection).文档对象 ...

  3. drupal7 Views Slideshow 简单教程

    一.下载安装(略) 二.内容类型建立(过程略,首页幻灯),字段建立(过程略)主要有2个字段,图片字段 和 指向链接字段 三.view 1.建立一个新的view,名称为frontbanner 显示为内容 ...

  4. distri.lua的web运维工具

    我的新手游项目很快就要进入到寻找发行商的环节,最近几天相对较空闲,逐将工作重心转移到服务器组运维工具的制作上. 回想一年之前经历的那个不算成功的端游项目,因为运维工具设计得不合理,使用十分不方便,游戏 ...

  5. CISA 信息系统审计知识点 [第二章. IT治理和管理 ]

    第二章.  IT治理和管理 1.  IT治理.管理.安全和控制框架及标准.指南和实践 IT治理是董事会和执行管理层的职责. IT治理的关键因素:保持与业务的战略一致,引导业务价值的实现. IT治理关注 ...

  6. 管理批量邮箱 FOXMAIL 和网易闪电邮(PC端)有什么区别? 对比

    喜欢用FOXMAIL有订阅功能<img src="https://pic1.zhimg.com/fa72df2440f84043a5275b90df30b2f4_b.jpg&q ...

  7. SQL Server 数据库操作类

    /// <summary> /// SQLServerHelper的摘要说明. /// </summary> public class SQLServerHelper { pu ...

  8. EPLAN 软件平台中的词“点“大全

    1. 中断点(Interruption Point):     在原理图绘制时,如果当前绘图区域的空间不足,需要转到其它页面继续绘制,而这两页之间存在连续的"信息流"时,可以使用& ...

  9. 最近一直在搞CAE,发现Eplan p8真的好强大。

    最近一直在搞CAE,发现Eplan p8真的好强大. 标准化的意义在与提高工作效率,减少重复. 标准化后,不容易出错,项目更改容易.事件都能及时跟踪.

  10. 从javascript一道闭包面试题说开去

    这道题目比较经典了: var a = 1; function test(){ a = 2; return function(){ console.log(a); } var a = 3; } test ...