第九章 方法
一、 实例构造器
1、 前面提到用new操作符创建对象时的三部曲:
l 为对象分配内存
l 初始化对象的附加成员(方法表指针和SyncBlockIndex)
l 调用实例构造器初始化实例状态
在分配内存时,系统将所有内存位置均置为0值,这就是为什么字段初始化而未赋值时均为0或null值。
不调用实例构造器的情况:
l 调用Object.MemberwiseClone()方法创建实例(分配内存;初始化附加成员;将源对象字节拷贝到新创建的对象)
l 反序列化对象时
2、 为避免为实例字段产生过多的构造器代码,应避免在声明字段时为字段符初值,而是在无参构造器中为它们符初值,在其他重载的构造器中调用无参构造器。
3、 值类型实例构造器
l C#编译器不会自动调用其构造器,必须显式调用构造器才能起作用
l C#编译器不允许为值类型定义无参实例构造器(下面会介绍可以定义无参类型构造器)
l 不能为结构中的字段在声明的同时赋初值,可通过定义带参构造器的方式进行
l 必须在结构的构造器中为所有字段赋初值
二、 类型构造器
1、 类型构造器的一些限制:
l 不能带任何参数
l 类型构造器总为私有的,不能用其他访问修饰符
2、类型构造器被调用的时机:
l 第一个实例被创建,或者类型的第一个字段或成员第一次被访问之前
l 非继承静态字段被第一次访问之前
类型构造器在类型的生命周期中只被调用一次;
3、一些限制:
l 若类型构造器中抛出异常,则该类型变成不可访问,访问其中的任何字段或方法均会抛出System.TypeInitializationException异常
l 类型构造器只能访问类型的静态字段
l 类型构造器不应该调用基类型的类型构造器,因静态字段并非继承而是编译时静态绑定
三、 操作符重载
1、 操作符重载
C#中对操作符重载的一些限制:
l 必须声明为public static
l 必须有一个参数为操作符所属类型
l 不能改变操作符原始定义的引数个数
l 若定义了true操作符也必须同时定义false操作符,二者都必须返回bool值
l ++、--操作符必须返回其所隶属之类型的一个实例
l 可被重载的一元操作符:+、-、!、~、++、--、true、false
l 可被重载的二元操作符:+、-、*、/、%、!、^(异或)、<、>、<<、>>、==、!=、<=、>=
l 不允许被重载的操作符:&&、||、=、?:、+=、-=、/=、%=、|=、^=、<<=、>>=,实际上其中一些“复式操作符”在二元操作符被重载后自动生成,而不能显式定义
l 必须成对重载的操作符:(== ,!=)、(<,>)、(<=,>=)
l ++、--操作符重载时不能区分其为前置或后置的
2、 操作符重载与语言互操作性
编译器会为重载的操作符生成一个特殊名称的方法,如+(加)操作符生成op_Addition()方法,并为该方法的定义条目上加上specialname标记。当某种语言不能进行操作符重载时,可以直接定义具有该特殊名称的方法,以在其他语言中调用;或直接调用具有该特殊名称的方法以适应某种语言不能解析操作符的限制。如:vb中不能重载操作符,可显式定义op_Addition()方法以在C#中调用;C#中定义的+操作符不能被VB识别,可显式调用op_Addition()方法获得同样的功能。
四、 转换操作符
转换操作符的一些限制:
l 必须为public static
l 必须指定关键字implicit或explicit,原则为:从本类型转换为其他类型使用implicit,将其他类型转换为本类型用explicit,不能都使用implicit
五、 方法参数
1、 引用参数
l 缺省情况下为值传递
l 标志为out的参数,在调用方法前不必初始化,但返回之前必须赋值,没有被初始化的参数是不能被使用
l 标志为ref的参数,在调用方法前必须初始化,否则触发编译错误
l 可以使用ref或out来进行方法的重载,但不能通过区分ref和out来重载方法
l 按引用方式传递的变量(实参)必须和方法声明的参数(形参)类型完全相同,否则触发编译错误。
2、 可变数目参数
使用params关键字及对象数组的方式指定可变参数序列。一些限制:
l 只有方法的最后一个参数才能使用可变数目参数
六、 虚方法
1、 虚方法的调用机理
CLR使用以下两个IL指令调用方法:
u call 根据类型(即引用的静态类型、声明类型)来调用一个方法
u callvirt 根据对象(即引用的动态类型、实际类型)来调用一个方法
对于虚方法使用call来调用的情况有:
l base.虚方法(),
l 密封类型引用虚方法,因为没有必要检验密封类型的实际类型
l 值类型,避免被装箱
使用callvirt调用非虚方法的情况:
l 应用变量为null时,使用callvirt才会抛出System.NullReferenceException异常,而call不会抛出
无论call或callvirt调用方法,均会有一个隐含的this指针作为方法的第一个参数,它指向正在操作的对象
2、 虚方法的版本控制:
用下面的例子说明:
using System;
class BaseClass
{
public void NonVirtualFunc()
{
Console.WriteLine("Non virtual func in base class");
}
public virtual void VirtualFunc()
{
Console.WriteLine("Virtual func in base class");
}
}
class DevicedClass : BaseClass
{
//若不使用new 关键字则编译器会有warning:
//“DevicedClass.NonVirtualFunc()”上要求关键字
//new,因为它隐藏了继承成员“BaseClass.NonVirtualFunc()”
public new void NonVirtualFunc()
{
Console.WriteLine("Non virtual func in deviced class");
}
//若不添加关键字override或new,则编译器会有warning:
//“DevicedClass.VirtualFunc()”将隐藏继承的成员“BaseClass.VirtualFunc()
//”。若要使当前成员重写该实现,请添加关键字 override。否则,添加关键字
//new。
public override void VirtualFunc()
{
Console.WriteLine("Virtual func in deviced class");
}
}
class TestClass
{
public static void Main()
{
//派生类实例调用 非虚 及 虚函数
DevicedClass dc = new DevicedClass();
dc.NonVirtualFunc();
dc.VirtualFunc();
//基类实例调用 非虚 及 虚函数
BaseClass bc = new BaseClass();
bc.NonVirtualFunc();
bc.VirtualFunc();
//指向派生类实例的基类引用 调用 非虚 及 虚函数
BaseClass bc1 = dc;
bc1.NonVirtualFunc();
bc1.VirtualFunc();
}
}
/*
在虚函数上使用关键字override的运行结果:
Non virtual func in deviced class
Virtual func in deviced class
Non virtual func in base class
Virtual func in base class
Non virtual func in base class
Virtual func in deviced class
*/
/*
在虚函数上使用关键字new的运行结果
Non virtual func in deviced class
Virtual func in deviced class
Non virtual func in base class
Virtual func in base class
Non virtual func in base class
Virtual func in base class
*/
由上可见:new 和 override在派生类中协调版本的控制,在第七章中已经看到oeverride只能用于virtual方法,new则可用于非虚或虚方法,以实现隐藏基类中的同名方法。在虚函数上使用override,重写了基类的方法,并无隐藏,这也就实现了多态。我们可设想这样的结论:new使用call指令调用静态类型的方法,而override使用callvirt指令调用动态类型的方法。
希望这个例子对您的理解有所帮助。