在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
方法代表在类型或类型的实例上执行某些操作的代码。 在类型上执行操作,称为静态方法;在类型的实例上执行操作,称为非静态方法。所有方法都有名称、签名和返回类型。 在判断方法的唯一性时,除了方法名之外都只以参数为准,方法返回类型会被忽略。 不过C#在定义转换操作符方法时实际放宽了这个操作。
编译器识别方法以下Employee类定义了三种不同的方法 internal class Employee{ //非虚实例方法 public Int32 GetYearsEmployed(){} //虚方法 public virtual String GetProgressReport{} //静态方法 public static Employee Lookup(String name){} } 编译以上代码,编译器会在程序集的方法定义表中写入3个记录项,每个记录项都用一组标值flag指明方法是实例方法、虚方法还是静态方法。 写代码调用这些方法,生成调用代码的编译器会检查方法定义的标值flag,判断应如何生成IL代码来正确调用方法。
CLR提供两个方法来调用指令call该IL指令可调用静态方法、实例方法和虚方法。
call指令假定该变量不为null,变量本身的类型指明了方法的定义类型。 如果变量的类型没有定义该方法,就检查基类型来查找匹配方法。 call指令经常用于以非虚方式调用虚方法。 callvirt该IL指令可调用实例方法和虚方法,不能调用静态方法。 用callvirt指令调用实例方法或虚方法,必须指定引用了对象的变量。 用callvirt指令调用非虚实例方法,变量的类型指定了方法的定义类型。 用callvirt指令调用虚实例方法,CLR调查发出调用的对象的实际类型,然后以多态方式调用方法。 为了确定类型,发出调用的变量决不能为null。换言之,编译这个调用时JIT编译器会生出代码来验证变量的值是不是null。 如果是,callvirt指令造成CLR抛出空引用异常。正是由于要进行这种额外的检查,所以callvirt指令的执行速度比call稍慢。 注意,即使callvirt指令调用的是非虚实例方法,也要执行这种null检查。 call和callvirt的实际使用调用静态方法,IL会调用call指令,调用虚实例方法、非虚实例方法时,IL调用 callvirt方法。 这意味着,当对象为null时,调用对象方法会抛出空引用异常。 但编译器有时用call而不是callvirt调用虚方法,下面代码证明了有时真的需要这样做 internal class SomeClass { //ToString是基类Object定义的虚方法 public override String ToString() { //编译器使用IL指令call //以非虚方式调用Object的ToString方法 //如果编译器用callvirt而不是 //那么该方法将递归调用自身,直至栈溢出 return base.ToString(); } } 调用虚方法base.ToString时,C#编译器生成call指令来确保以非虚方式调用基类的ToString方法。 这是必要的,因为如果以虚方式调用ToString,调用会递归执行。 值类型倾向使用call编译器调用值类型定义的方法时倾向于使用call指令,因为值类型是密封的。 这意味着即使值类型含有虚方法也不要考虑多态性,这使调用更快。 此外,值类型实例的本质保证他永不为null,所以永不抛出空引用异常。 如果以虚方式调用值类型中的虚方法,CLR要获取对值类型的类型对象的引用,以便引用(类型对象中的)方法表,这要求对值类型装箱。 装箱对堆造成更大压力,迫使更频繁的垃圾回收,使性能受到影响。 无论用call还是callvirt调用实例方法还是虚方法,这些方法通常接收隐藏的this实参作为方法的第一个参数。this实参引用要操作的对象。 类型的设计原则类型设计的时候应尽量减少虚方法数量。
如果希望这些方法是多态的,最好的办法就是使最复杂的方法成为虚方法,使所有重载的简便方法成为非虚方法。
隐藏基类的同名实例方法假设Phone类型定义了Dial方法 public class Phone{ public void Dial() => Console.WriteLine("Phone.Dial"); } 假设BetterPhonej以Phone类型作为基类型 public class BetterPhone:Phone{ public void Dial() => Console.WriteLine("BetterPhone.Dial"); } 编译上述代码时,C#编译器会警告BetterPhone类正在定义这个Dial方法,他会隐藏Phone类定义的Dial。 解决办法就是在BetterPhone类中定义Dial时,在前面加一个new关键字。 public new void Dial() => Console.WriteLine("BetterPhone.Dial"); //测试类 BetterPhone bp = new BetterPhone(); bp.Dial(); //结果 BetterPhone.Dial
隐藏基类同名的虚方法假定Phone类型添加虚方法Message,BetterPhone重写了该方法。并且BetterPhone的Dial方法调用了Message方法。 public class Phone{ public void Dial(){ Console.WriteLine("Phone.Dial"); Message(); } public virtual void Message() => Console.WriteLine("Phone.Message"); } public class BetterPhone : Phone{ public new void Dial(){ Console.WriteLine("BetterPhone.Dial"); base.Dial(); } public virtual void Message() => Console.WriteLine("BetterPhone.Message"); } BetterPhone类型会生成警告,要给BetterPhone的Message方法删除virtual关键字添加override关键字,或者直接添加new关键字 添加new关键字 public new virtual void Message() => aConsole.WriteLine("BetterPhone.Message"); //测试结果 BetterPhone.Dial Phone.Dial Phone.Message 在这段代码中,关键字new告诉编译器生成元数据,让CLR知道BetterPhone类型的Message方法应被视为由BetterPhone类型引入的新函数。 删除virtual关键字添加override关键字 public overridel void Message() => aConsole.WriteLine("BetterPhone.Message"); //测试结果 BetterPhone.Dial Phone.Dial BetterPhone.Message
|
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论