【C#进阶系列】08 方法
实例构造与引用类型
之前的章节其实已经写过了引用类型的构造过程:
首先当然是,在堆中,为引用类型的实例对象分配内存,然后初始化对象的附加字段(即类型对象指针和同步块索引)。
这个时候为对象分配的内存都是直接被置为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类的定义去理解。
关于扩展方法
先说一下我自己的认识吧,其实我不建议使用这个东西。
因为写得不规范的扩展方法,会增加了代码的阅读难度,增加维护成本。(我真的很确定有的人会把这个东西写得到处都是)
这个东西之前在写《重构》的学习笔记中提到过,主要用于解决别人封装的类库,没法增加自己想要的函数。
简单来讲,还是慎用,自己写的类就别用扩展方法。
另外扩展方法必须是顶级静态类中定义的静态方法,如果是嵌套类中的话,编译会出错。
实际上扩展方法在C#编译器编译过后也只是个一般的静态对象里的静态函数,只不过加了个[Extension]的特性。然而实际上这个ExtensionAttribute特性还不能在代码中用,都是C#编译器去自动生成的。
关于分部方法
分部方法和分部类很像,不过是方法前面加上partial修饰符。
这样的话,如果其它分部类实现了这个方法,那么就会加上这个方法,如果没有实现,那么这条代码在编译的时候就会被忽略。
但是分部方法只能在分部类和结构中用,且返回类型总是void,任何参数都不能用out来修饰。之所以会这样限制,是因为方法在运行时可能就并不存在,所以也就不会有返回。
分部方法总是private的,但是C#编译器禁止将private修饰符显式写在分部方法前面。(和类型构造器在这个点上类似)
【C#进阶系列】08 方法的更多相关文章
- 【 D3.js 进阶系列 — 1.2 】 读取 CSV 文件时乱码的解决方法
在 D3 中使用 d3.csv 读取 CSV 文件时,有时会出现乱码问题. 怎么解决呢? 1. 乱码问题 使用 d3.csv 读取 xxx.csv 文件时.假设 xxx.csv 文件使用的是 UTF- ...
- C#进阶系列 ---- 《CLR via C#》
[C#进阶系列]30 学习总结 [C#进阶系列]29 混合线程同步构造 [C#进阶系列]28 基元线程同步构造 [C#进阶系列]27 I/O限制的异步操作 [C#进阶系列]26 计算限制的异步操作 ...
- C#进阶系列——WebApi 接口返回值不困惑:返回值类型详解
前言:已经有一个月没写点什么了,感觉心里空落落的.今天再来篇干货,想要学习Webapi的园友们速速动起来,跟着博主一起来学习吧.之前分享过一篇 C#进阶系列——WebApi接口传参不再困惑:传参详解 ...
- C#进阶系列——WebApi 接口参数不再困惑:传参详解
前言:还记得刚使用WebApi那会儿,被它的传参机制折腾了好久,查阅了半天资料.如今,使用WebApi也有段时间了,今天就记录下API接口传参的一些方式方法,算是一个笔记,也希望能帮初学者少走弯路.本 ...
- C#进阶系列——WebApi 接口测试工具:WebApiTestClient
前言:这两天在整WebApi的服务,由于调用方是Android客户端,Android开发人员也不懂C#语法,API里面的接口也不能直接给他们看,没办法,只有整个详细一点的文档呗.由于接口个数有点多,每 ...
- C#进阶系列——WebApi 跨域问题解决方案:CORS
前言:上篇总结了下WebApi的接口测试工具的使用,这篇接着来看看WebAPI的另一个常见问题:跨域问题.本篇主要从实例的角度分享下CORS解决跨域问题一些细节. WebApi系列文章 C#进阶系列— ...
- C#进阶系列——WebApi 身份认证解决方案:Basic基础认证
前言:最近,讨论到数据库安全的问题,于是就引出了WebApi服务没有加任何验证的问题.也就是说,任何人只要知道了接口的url,都能够模拟http请求去访问我们的服务接口,从而去增删改查数据库,这后果想 ...
- C#进阶系列——WebApi 异常处理解决方案
前言:上篇C#进阶系列——WebApi接口传参不再困惑:传参详解介绍了WebApi参数的传递,这篇来看看WebApi里面异常的处理.关于异常处理,作为程序员的我们肯定不陌生,记得在介绍 AOP 的时候 ...
- C#进阶系列——WebApi 路由机制剖析:你准备好了吗?
前言:从MVC到WebApi,路由机制一直是伴随着这些技术的一个重要组成部分. 它可以很简单:如果你仅仅只需要会用一些简单的路由,如/Home/Index,那么你只需要配置一个默认路由就能简单搞定: ...
- C#进阶系列——一步一步封装自己的HtmlHelper组件:BootstrapHelper(三:附源码)
前言:之前的两篇封装了一些基础的表单组件,这篇继续来封装几个基于bootstrap的其他组件.和上篇不同的是,这篇的有几个组件需要某些js文件的支持. 本文原创地址:http://www.cnblog ...
随机推荐
- Java Inner Classes
When thinking about inner classes in java, the first thing that comes to my mind is that, WHY do we ...
- Openvswitch原理与代码分析(1):总体架构
一.Opevswitch总体架构 Openvswitch的架构网上有如下的图表示: 每个模块都有不同的功能 ovs-vswitchd 为主要模块,实现交换机的守护进程daemon ...
- 学习之路三十九:新手学习 - Windows API
来到了新公司,一开始就要做个程序去获取另外一个程序里的数据,哇,挑战性很大. 经过两周的学习,终于搞定,主要还是对Windows API有了更多的了解. 文中所有的消息常量,API,结构体都整理出来了 ...
- Hadoop - Azkaban 作业调度
1.概述 在调度 Hadoop 的相关作业时,有以下几种方式: 基于 Linux 系统级别的 Crontab. Java 应用级别的 Quartz. 第三方的调度系统. 自行开发 Hadoop 应用调 ...
- Cocos2d-JS项目之四:UI界面的优化
测试环境: iphone4.iOS6.1.2.chrome 37.2062.60,Cocos2d-js 3.6 之前写了不少,实际项目也按这个去优化了,也有效果,但到最后才发现,尼玛,之前都搞错了,之 ...
- 命令行下使用javah命令生成.h文件,出现“错误: 无法访问android.app.Activity 找不到android.app.Activity的类文件”的解决方法
在学习NDK中,当我在项目的bin/classes目录下使用javah命令生成头文件时,出现了“错误: 无法访问android.app.Activity 找不到android.app.Activity ...
- 飞思卡尔9S12X系列双核中的协处理器XGATE使用方法
http://adi.chinaaet.com/analog/blogdetail/24482.html
- Http 1.1协议
HTTP是hypertext transfer protocol(超文本传输协议)的简写,它是TCP/IP协议的一个应用层协议,用于定义WEB浏览器与WEB服务器之间数据交换的过程. 1.Http1. ...
- 再谈LRU双链表内存管理
N年前我写了个双链表也发了博客,还添了代码.但是那个代码不但复杂,而且还有有问题的,一直懒得整理,放在空间误导别人.最近在写服务端,今天抽点空补一篇. 关于LRU网上随便搜,有过后端经验的人应该很多都 ...
- C# 解析百度天气数据,Rss解析百度新闻以及根据IP获取所在城市
百度天气 接口地址:http://api.map.baidu.com/telematics/v3/weather?location=上海&output=json&ak=hXWAgbsC ...