引用:
C#首席架构师:版本控制、虚拟化和覆盖
网址:http://www.youkuaiyun.com/article/2012-09-17/2810048-Anders-Hejlsberg-on-Versioning-Virtual-O
Bill Venners:在Java中,实例方法默认是虚拟化的,只有显式声明为final时,才可以在子类中被覆盖。相比之下,C#中实例方法默认为非虚拟化,要想声明虚拟化方法需要显式声明它为虚拟的。为什么要这样做?
Anders Hejlsberg:原因在于以下几点:
首先是性能。通过观察,我们发现用Java程序员在写代码时常常会忘记将方法声明为final,因此这些方法将是虚拟化的,所以性能并不好。相比于使用虚拟方法这样会带来性能上的提升。这是其一。
更重要的一点在于版本控制。关于虚拟方法主要有两派思想:一是学院派,他们认为“任何事物都应该是虚拟化的,因为我可能会在将来覆盖它”;务实派(从现实世界中获取开发应用的思路)认为“我们只需要关心有必要的虚拟化”。
当我们将平台中某物虚拟化时,对未来的变化做了过多的假设和保留。对于一个非虚拟化的方法,在你调用它时会发生x、y;而在API中发布一个虚拟化的方法时,不仅在你调用该方法时会发生x、y。我们同样保证当你在覆盖这个方法时,会在这个特定的序列中调用它,注意到这些状态不变条件。
每当使用到API中的虚拟化时,你就是在创造一个回调的hook。作为一个OS或者API框架设计师,更需要注意这些。你不会希望用户在API任何部分重载和hook,因为你不一定能保证这些。同样,人们在虚拟化声明某物时也并不一定完全了解他们自己做的保证。
C# 语言经过专门设计,以便不同库中的基类与派生类之间的版本控制可以不断向前发展,同时保持向后兼容。这具有多方面的意义。例如,这意味着在基类中引入与派生类中的某个成员具有相同名称的新成员在 C# 中是完全支持的,不会导致意外行为。它还意味着类必须显式声明某方法是要重写一个继承方法,还是一个隐藏具有类似名称的继承方法的新方法。
在 C# 中,派生类可以包含与基类方法同名的方法。
-
基类方法必须定义为 virtual。
-
如果派生类中的方法前面没有 new 或 override 关键字,则编译器将发出警告,该方法将有如存在 new 关键字一样执行操作。
-
如果派生类中的方法前面带有 new 关键字,则该方法被定义为独立于基类中的方法。
-
如果派生类中的方法前面带有 override 关键字,则派生类的对象将调用该方法,而不是调用基类方法。
-
可以从派生类中使用 base 关键字调用基类方法。
-
override、virtual 和 new 关键字还可以用于属性、索引器和事件中。
new 关键字:
class Father
{
public int getInt()
{
Console.WriteLine("Father: getInt()");
return 2;
}
public int setInt()
{
Console.WriteLine("Father: setInt()");
return 3;
}
}
//---------------------------------------------------------------------
class Son : Father
{
public new int getInt()
{
Console.WriteLine("Son: getInt()");
return 1;
}
}
//-------------------测试----------------------------------------------------
class Program
{
static void Main(string[] args)
{
Father fs = new Son();
Console.WriteLine(fs.getInt());
Console.WriteLine(fs.setInt());
Console.WriteLine("--------------------------------------------------------");
Son fs2 = new Son();
Console.WriteLine(fs2.getInt());
Console.WriteLine(fs2.setInt());
Console.ReadLine();
}
}
输出结果: Father: getInt() 2 Father: setInt() 3 ---------------------------------------------- Son: getInt() 1 Father: setInt() 3
Virtural 与 Override 关键字
class Father {
public int getInt()
{
Console.WriteLine("Father: getInt()");
return 2;
}
virtual public int setInt()
{
Console.WriteLine("Father: setInt()");
return 3;
}
}
class Son : Father
{
public new int getInt()
{
Console.WriteLine("Son: getInt()");
return 1;
}
public override int setInt()
{
Console.WriteLine("Son: setInt()");
return 4;
}
}
class Program
{
static void Main(string[] args)
{
Father fs = new Son();
Console.WriteLine(fs.getInt());
Console.WriteLine(fs.setInt());
Console.WriteLine("--------------------------------------------------------");
Son fs2 = new Son();
Console.WriteLine(fs2.getInt());
Console.WriteLine(fs2.setInt());
Console.ReadLine();
}
}
/*
输出结果:
Father: getInt()
2
Son: setInt()
4
----------------------------------------------
Son: getInt()
1
Son: setInt()
4
*/
-
接口中,子类显示实现接口,则为接口调用
-
隐式实现接口,则为类自己调用
public interface IReview {
void GetReviews();
}
public class ShopReview :IReview {
public void GetReviews(){}
}
//这种方式是隐示实现:
IReview rv = new ShopReview();rv.GetReviews();
ShopReview rv = new ShopReview();rv.GetReviews();
//都可以调用GetReviews这个方法。
//还有一种方式是显示实现:
public interface IReview {
void GetReviews();
}
public class ShopReview :IReview {
void IReview.GetReviews(){}
}
//通过这种方式的接口实现。
//GetReviews就只能通过接口来调用:
IReview rv = new ShopReview();rv.GetReviews();
//下面的这种方式将会编译错误
ShopReview rv = new ShopReview();rv.GetReviews();
/*
结论:
隐示实现接口和类都可以访问
显示实现只有接口可以访问。
显示实现益处
1:隐藏代码的实现
2:在使用接口访问的系统中,调用者只能通过接口调用而不是底层的类来访问。
*/
默认情况下,C# 方法为非虚方法。如果某个方法被声明为虚方法,则继承该方法的任何类都可以实现它自己的版本。若要使方法成为虚方法,必须在基类的方法声明中使用 virtual 修饰符。然后,派生类可以使用 override 关键字重写基虚方法,或使用 new 关键字隐藏基类中的虚方法。如果 override 关键字和 new 关键字均未指定,编译器将发出警告,并且派生类中的方法将隐藏基类中的方法。

本文详细解析了C#中实例方法的虚拟化与覆盖机制,包括性能优化、版本控制、新关键字的应用及接口实现的区别。通过具体代码示例展示了如何在C#中实现方法的虚拟化和覆盖,以及接口的隐式与显示实现。

2433

被折叠的 条评论
为什么被折叠?



