《CLR via C#》设计类型.方法

8.方法

8.1类型的实例构造器方法(引用类型)

类型的实例构造器用来设置对象的初始状态。实例构造器不能被继承。如果类没有显示定义任何构造器,C#编译器才会定义一个默认的无参构造器,也就是单纯地调用了基类的无参构造器:

public class SomeType
{
	public SpmeType() : base() {}
}
// 由于C#编译器会默认生成无参构造器,所以上面这种写法等价于下面的写法:
public class SomeType {}

但是,如果基类里面没有无参构造器,也就是没有base(),那么子类必须显示调用一个基类的构造器(写法:public SpmeType(XXX) : base(参数) {XXX}),否则编译会报错。

C#编译器允许在引用类型中以内联方式(一种简单的语法)初始化实例字段,但在幕后,C#编译器会将这种语法转换成构造器方法中的代码来执行初始化。举例:

internal sealed class SomeType
{
	private int m_x =5;
}

实际上是在构造器的代码的最开始处,将5赋值给字段m_x的,此构造器再调用了基类的构造器(SomeType的基类是Object,虽然Object的构造器啥也不做,也得调用,因为不管基类是什么,都得调用基类构造器来初始化基类中的实例字段),C#编译器会在 默认无参构造器 或 每个自定义的实例构造器 上自动添加基类的无参构造器":base()",如果基类没有无参构造器,子类就得自己显示调用一个基类的构造器。
再举个例:
在这里插入图片描述
C#编译器为这三个构造器生成代码时,有三步:1.前三个字段的初始化工作都将在每个构造器的开始位置进行;2.每个构造器都会调用基类(Object)的构造器;3.每个构造器执行自己的代码,对于第三个构造器来说,就是用10覆盖掉了m_d原先值3.14159。但上述这种写法会导致代码冗余,因为每个构造器都生成了初始化前三个字段的代码。
所以,如果有几个在定义时就已初始化的实例字段和许多重载的构造函数,可考虑不在定义字段的时候初始化,而是创建一个公共初始化构造器来执行这些字段的初始化,然后,让其他构造器都通过:this()显示调用这个公共初始化构造器,这样能减少生成的代码。举例:
在这里插入图片描述

8.2结构的实例构造器方法(值类型)

和类型的实例构造器方法有些不同,但结构的实例构造器方法基本用不上,用到的时候,看看书就好。

8.3类型构造器方法(又叫静态构造器)

实例构造器的作用是设置类型的实例的初始状态,而类型构造器的作用是设置类型的初始状态。
类型构造器一般用于引用类型,虽然能用于值类型,但没意义。
C#编译器不会生成默认的类型构造器,只能自己定义,且最多只能定义一个,且不能有参数,且不能加访问修饰符(默认为private)。
一般放在非静态类里使用,用于初始化非静态类里的静态字段,只会被执行一次,也就是在创建类的第一个实例或引用类的任何静态成员时被执行一次。

类型构造器也可以以内联方式初始化静态字段。

internal sealed class SomeType{
	private static int s_x = 5;
}

类型构造器不会也不应该调用基类型的类型构造器,因为子类型不可能有静态字段是从基类型中继承的。
实际应用: 非常适合在类型构造器中初始化类型需要的任何单例对象:https://blog.csdn.net/BillCYJ/article/details/90242483
单例模式介绍:https://blog.csdn.net/BillCYJ/article/details/79394582

8.4操作符重载方法

操作符和操作符重载都属于语言层面,CLR对它们都一无所知。
操作符重载方法的要求: 操作符重载方法必须是public和static的;C#编译器要求操作符重载方法至少有一个参数的类型与操作符重载方法所在的类型相同。
编程语言的编译器看到源码中出现一个+操作符时,会检查是否有一个操作数的类型定义了一个名为op_Addtion的specialname方法(补充:op_Addition、op_Multiply等都统称为specialname方法),而且该方法满足操作符重载方法的要求。如果存在这样的一个方法,编译器就生成调用它的代码。如果不存在这样的一个方法,就生成一个编译错误。

举例:

class Program
{
    static void Main(string[] args)
    {
        RewritePlus num1 = new RewritePlus(4);
        RewritePlus num2 = new RewritePlus(9);
        RewritePlus num3 = num1 + num2;
        Console.WriteLine(num3.Value);
        Console.ReadKey();
    }
}

public sealed class RewritePlus
{
    private int mValue = 0;
    public int Value
    {
        get { return mValue; }
        set { mValue = value; }
    }

    public RewritePlus(int value)
    {
        Value = value;
    }

	// 要重写哪个类型的操作符,就把操作符重载方法放在哪个类型里
    public static RewritePlus operator +(RewritePlus num1, RewritePlus num2) 
    {
        return new RewritePlus(num1.Value - num2.Value); // 重载操作符为减法
    }
}

输出-5

上面Main方法的IL部分代码截图:
在这里插入图片描述
上图可以看见num1 + num2的+号调用的是RewritePlus类的op_Addition方法。由下图可以看见+号重载方法被编译成了op_Addition。
在这里插入图片描述

8.5类型转换操作符方法

当设计一个类型时应该考虑到和其它类型之间需要支持的转换,就像上面的RewritePlus类型一样,如果能将一个int或者double转换为一个RewritePlus,就会很方便,反之亦然。

思考:从int类型到long类型的转换为什么就可以隐式的进行呢?从long类型到int类型的转换为什么就可以显示的进行呢?
在C#中,implicit关键字告诉编译器为了生成代码来调用方法,不需要在源代码中进行显示转换;相反,explicit关键字告诉编译器只有在发现了显示转型时,才调用方法。
在implicit或explicit关键字之后,operator关键字告诉编译器该方法是一个转换操作符。在operator之后,指定对象要转换成什么类型。在圆括号内,则指定要从什么类型转换。举例:

public static implicit operator RewritePlus(double value) // 支持double类型隐式转换成RewritePlus类型
{
    return new RewritePlus(value);
}
// 在Main中:
rational r2 = 1.1; // 正确

如果不实现上面的类型转换操作符方法,就会报错:无法将类型“double”隐式转换为“Chapter8Method.RewritePlus”
https://www.cnblogs.com/skm-blog/p/4229487.html 这篇文章讲得不错

结论: 设计类型时,不论是操作符重载还是转换操作符,都是为了编码方便。

8.6扩展方法

从例子中学习:StringBuilder类提供的字符串处理方法比String类少,假设你想扩展一些方法以方便操作StringBuilder,你可以这样做:

// 扩展StringBuilder类的IndexOf方法,用于返回指定字符串中第一个指定字符的索引值。
public static class StringBuilderExtensions
{
    public static int IndexOf(this StringBuilder sb, char c)
    {
        for (int i = 0; i < sb.Length; ++i)
            if (sb[i] == c)
                return i;
        return -1;
    }
}
sealed class Process
{
    static void Main(string[] args)
    {
        StringBuilder sb = new StringBuilder("jumpchen");
        // 先检查StringBuilder类及它的全部基类里是否提供了IndexOf(),如果没有,才会调用这个扩展方法。
        Console.WriteLine(sb.Replace('u', 'h').IndexOf('h'));
        Console.ReadKey();
    } 
}
// 输出1

扩展方法必须是静态方法,必须在非泛型的静态类中使用(类名无限制),至少有一个参数,第一个参数前加this,this指向第一个参数的类型,所以,VS的智能感知窗口会智能提示可用于句点(".")左侧对象的类型的扩展方法。C#只支持扩展方法,不支持扩展属性、事件、操作符等。
注意: 一定要在使用静态方法时,主动在脚本顶部加上"using 静态方法所在的静态类名;",这样可以大大地提升性能,省去了匹配所有静态方法的运算量。
另外,还可以为接口类型、委托类型和枚举类型定义拓展方法。扩展方法是Linq技术的基础。

猜你喜欢

转载自blog.csdn.net/BillCYJ/article/details/90644214