【CLR via C#】第8章-方法

1.创建引用类型的实例时,首先为实例的数据字段分配内存,然后初始化对象的附加字段(类型对象指针和同步块索引),最后调用类型的实例构造器来设置对象的初始状态。
2.和其他方法不同,实例构造器永远不能被继承

也就是说,类只有自己定义的实例构造器。因为不能被继承,所以实例构造器不能使用以下修饰符:virtual,new,override,sealed和abstract

3.如果类的修饰符为abstract,那么编译器生成的默认构造器的可访问性就为protected。 如果类的修饰符为static(sealed,abstract),那么编译器根本不会在类的定义中生成默认构造器。 如果类没有显示定义任何构造函数,C#编译器将定义一个默认无参构造器

在它的实现里,只是简单调用了基类的无参构造器。

public class SomeType{
}

// 等价于
public class SomeType{
	public SomeType() : base() {}
}
4.为了使代码“可验证(verifiable)”,类的实例构造器在访问从基类继承的任何字段之前,必须先调用基类的构造器,即使派生类的构造器没有显示调用基类构造器,C#编译器也会自动生成对默认的基类构造器的调用。最终,System.Object的公共无参构造器会得到调用,尽管它什么都不做。

极少数时候可以在不调用实例构造器的前提下创建类型的实例,比如:Object.MemberwiseClone,System.Rutime.Serialization.FormatterServices中的GetUninitializedObjectGetSafeUninitializedObject

5.C#编译器提供了一个简化的语法,允许以“内联”(即嵌入)方法初始化实例字段。但是幕后,它会将这种语法转换成构造器方法中的代码来执行初始化。所以当有多个构造器时,我们应当注意代码膨胀
internal sealed class SomeType{
	private int m_x = 5;
	private string m_s = "Hi there";
	private double m_d = 3.14159;
	private byte m_b;

	public SomeType(){}
	public SomeType(int x){}
	public SomeType(string s){ m_d = 10; }
}

在上面三个构造器中,编译器为每个构造器方法生成初始化m_x,m_s,m_d的代码,这是多余的。
在这些代码初始化后,编译器会插入对基类构造器的调用,再然后才回插入自己构造器的代码。
例如,对于public SomeType(string s)的构造器,编译器生成的代码首先初始化m_x,m_s,m_d,再调用基类(object)的构造器,在执行自己的代码,这里是覆盖m_d的值。
可以用优化为以下代码

internal sealed class SomeType{
	private int m_x = 5;
	private string m_s = "Hi there";
	private double m_d = 3.14159;
	private byte m_b;

	public SomeType(){
		m_x = 5;
		m_s = "Hi there";
		m_d = 3.14159;
		m_b = 0xff;
	}
	public SomeType(int x) : this() {}
	public SomeType(string s) : this() { m_d = 10; }
}
6.对于值类型结构体(struct),编译器不会为其生成默认的构造器,并且即使定义了构造器,也要显式调用才会执行。
internal struct Point{
	public int m_x, m_y;
	public Point(int x, int y){
		m_x = x;
		m_y = y;
	}
}

internal sealed class Rectangle{
	public Point m_topLeft, m_bottomRight;
	public Rectangle(){
		// 需要显式调用
		m_topLeft = new Point(1, 2);
		m_bottomRight = new Right(100, 200);
	}
}

对于下面的代码,m_x,m_y字段将被初始化为0,因为没有显式调用Point构造器

internal struct Point{
	public int m_x, m_y;
	public Point(){
		m_x = 5;
		m_y = 6;
	}
}

internal sealed class Rectangle{
	public Point m_topLeft, m_bottomRight;
	public Rectangle(){}
}

当然你会发现上面的代码编译不能通过,编译器会提示错误“结构不能包含显式的无参数构造器”。
值得注意的是,虽然C#不允许值类型带有无参构造器,但是CLR允许,所以你可以用IL来定义无参构造器。
由于C#不允许值类型定义无参构造器,所以以下类型也会编译失败:

internal struct SomeValType{
	// 结构中不能有实例字段初始值设定项
	private int m_x = 5;
}

当然,在访问值类型的任何字段之前,需要对全部字段进行赋值,否则编译器报错:在控制返回到调用方之前,字段“SomeValType.m_y”必须完全赋值。

internal struct SomeValType{
	private int m_x, m_y;
	
	// C#允许定义有参构造器
	public SomeValType(int x){
		m_x = x;
		// m_y没有初始化
	}
}

可以用下方案

public SomeValType(int x){
	this = new SomeValType();	// 这里字段都会被初始化为0/null

	m_x = x; // 用x覆盖m_x的0
}

this代表值类型本身的一个实例,用new创建的值类型的一个实例可以赋值给this。在new的过程中,会将所有字段设置为0。而在引用类型中this被认为是只读的,所以不能被赋值。

7.类型构造器,也称静态构造器(即静态构造函数)C#总是会把它设置为private的,因为对它的调用是由CLR负责的。类型构造器是线程安全的,CLR保证它有且被执行一次
internal sealed class SomeType{
	private static int s_x = 5;
	static SomeType(){
		s_x = 10;
	}
}

在上述列子中,C#编译器只生成一个类型构造器方法。它首先将s_x初始化为5,再把它修改为10。也就是说,当C#编译器为类型构造器生成IL代码时,它首先生成的是初始化静态字段所需的代码,然后才会添加你的类型构造器方法中显式包含的代码。

8.转换操作符是将对象从一张类型转换成另一种类型的方法,并且CLR规范要求转换操作符重载方法必须是publicstatic方法。
public sealed class Rational
{
    public Rational(int num) { }

    private float toFloat() => float.MaxValue;

    /// <summary>
    /// 由一个int隐式构造并返回Rational
    /// </summary>
    /// <param name="num"></param>
    public static implicit operator Rational(int num)
    {
        return new Rational(num);
    }

    /// <summary>
    /// 由一个Rational显式返回float
    /// </summary>
    /// <param name="num"></param>
    public static explicit operator float(Rational num)
    {
        return num.toFloat();
    }
}

C#中,implicit关键字告诉编译器为了生成代码来调用方法,不需要在源代码中进行显示转型。相反,explicit关键字告诉编译器只有在发现了显式转型时,才调用方法。

Rational r = 5;
float f = (float)r;
9.扩展方法可以定义在接口、枚举和委托上。

任何表达式,只要它最终的类型实现了IEnumerable<T>接口,就能调用下面的扩展方法。

public static void ShowItems<T>(this IEnumerable<T> collection){
	foreach (var item in collection)
		Console.WriteLine(item);
}

// 下面展示如何调用
Action<object> action = o => Console.WriteLine(o.GetType());	// 抛出NullReferenceException
action.InvokeAndCatch<NullReferenceException>(null);	// 不会抛出错误,因为异常被捕获了

并且C#编译器允许创建委托来引用一个对象上的扩展方法

public static void Main(){
	Action a = "Jeff".ShowItems;
}

在C#中,一旦用this关键字标记了某个静态方法的第一个参数,编译器就会在在内部向该方法应用一个定制特性[Extension]。该特性会在最终生成文件的元数据中持久性地存储下来。不仅如此,任何静态类只要包含了至少一个扩展方法,它的元数据中也会应用这个特性,当然任何程序集中只要包含了一个符合上述特点的静态类,它的元数据中也会应用这个特性。这样一来,编译器就能快速扫描引用的程序集,判断哪些包含了扩展方法。

namespace System.Runtime.CompilerServices
{
  [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method)]
  public sealed class ExtensionAttribute : Attribute
  {
  }
}
10.分布方法只能在分部类或结构中声明。其返回类型始终是void,任何参数都不能用out,但是可以有ref修饰,也可以是泛型方法,实例或者静态方法,并且可以标记为unsafe
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值