C# 语言经过专门设计,以便不同库中的基类与派生类之间的版本控制可以不断向前发展,同时保持向后兼容。这具有多方面的意义。例如,这意味着在基类中引入与派生类中的某个成员具有相同名称的新成员在 C# 中是完全支持的,不会导致意外行为。它还意味着类必须显式声明某方法是要重写一个继承方法,还是一个隐藏具有类似名称的继承方法的新方法。
在 C# 中,派生类可以包含与基类方法同名的方法。
基类方法必须定义为 virtual。
- 如果派生类中的方法前面没有 new 或 override 关键字,则编译器将发出警告,该方法将有如存在 new 关键字一样执行操作。
- 如果派生类中的方法前面带有 new 关键字,则该方法被定义为独立于基类中的方法。
- 如果派生类中的方法前面带有 override 关键字,则派生类的对象将调用该方法,而不是调用基类方法。
可以从派生类中使用 base 关键字调用基类方法。
override、virtual 和 new 关键字还可以用于属性、索引器和事件中。
默认情况下,C# 方法为非虚方法。如果某个方法被声明为虚方法,则继承该方法的任何类都可以实现它自己的版本。若要使方法成为虚方法,必须在基类的方法声明中使用 virtual 修饰符。然后,派生类可以使用 override 关键字重写基虚方法,或使用 new 关键字隐藏基类中的虚方法。如果 override 关键字和 new 关键字均未指定,编译器将发出警告,并且派生类中的方法将隐藏基类中的方法。
为了在实践中演示上述情况,我们暂时假定公司 A 创建了一个名为 GraphicsClass 的类,您的程序将使用此类。 GraphicsClass 如下所示:
class GraphicsClass { public virtual void DrawLine() { } public virtual void DrawPoint() { } }</div>
您的公司使用此类,并且您在添加新方法时将其用来派生自己的类:
class YourDerivedGraphicsClass : GraphicsClass { public void DrawRectangle() { } }</div>
您的应用程序运行正常,直到公司 A 发布了 GraphicsClass 的新版本,类似于下面的代码:
class GraphicsClass { public virtual void DrawLine() { } public virtual void DrawPoint() { } public virtual void DrawRectangle() { } }</div>
现在,GraphicsClass 的新版本中包含一个名为 DrawRectangle 的方法。开始时,没有出现任何问题。新版本仍然与旧版本保持二进制兼容。已经部署的任何软件都将继续正常工作,即使新类已安装到这些软件所在的计算机系统上。在您的派生类中,对方法 DrawRectangle 的任何现有调用将继续引用您的版本。
但是,一旦您使用 GraphicsClass 的新版本重新编译应用程序,就会收到来自编译器的警告 CS0108。此警告提示您必须考虑希望 DrawRectangle 方法在应用程序中的工作方式。
如果您希望自己的方法重写新的基类方法,请使用 override 关键字:
class YourDerivedGraphicsClass : GraphicsClass { public override void DrawRectangle() { } }</div>
override 关键字可确保派生自 YourDerivedGraphicsClass 的任何对象都将使用 DrawRectangle 的派生类版本。派生自 YourDerivedGraphicsClass 的对象仍可以使用基关键字访问 DrawRectangle 的基类版本:
base.DrawRectangle();</div>
如果您不希望自己的方法重写新的基类方法,则需要注意以下事项。为了避免这两个方法之间发生混淆,可以重命名您的方法。这可能很耗费时间且容易出错,而且在某些情况下并不可行。但是,如果您的项目相对较小,则可以使用 Visual Studio 的重构选项来重命名方法。
或者,也可以通过在派生类定义中使用关键字 new 来防止出现该警告:
class YourDerivedGraphicsClass : GraphicsClass { public new void DrawRectangle() { } }</div>
使用 new 关键字可告诉编译器您的定义将隐藏基类中包含的定义。这是默认行为。
重写和方法选择
当在类中指定方法时,如果有多个方法与调用兼容(例如,存在两种同名的方法,并且其参数与传递的参数兼容),则 C# 编译器将选择最佳方法进行调用。下面的方法将是兼容的:
public class Derived : Base { public override void DoWork(int param) { } public void DoWork(double param) { } }</div>
在 Derived 的一个实例中调用 DoWork 时,C# 编译器将首先尝试使该调用与最初在 Derived 上声明的 DoWork 版本兼容。重写方法不被视为是在类上进行声明的,而是在基类上声明的方法的新实现。仅当 C# 编译器无法将方法调用与 Derived 上的原始方法匹配时,它才尝试将该调用与具有相同名称和兼容参数的重写方法匹配。例如:
int val = 5; Derived d = new Derived(); d.DoWork(val); // Calls DoWork(double).</div>
由于变量 val 可以隐式转换为 double 类型,因此 C# 编译器将调用 DoWork(double),而不是 DoWork(int)。有两种方法可以避免此情况。首先,避免将新方法声明为与虚方法同名。其次,可以通过将 Derived 的实例强制转换为 Base 来使 C# 编译器搜索基类方法列表,从而使其调用虚方法。由于是虚方法,因此将调用 Derived 上的 DoWork(int) 的实现。例如:
((Base)d).DoWork(val); // Calls DoWork(int) on Derived.</div>
何时使用 Override 和 New 关键字
在 C# 中,派生类中方法的名称可与基类中方法的名称相同。可通过使用 new 和 override 关键字指定方法互动的方式。 override 修饰符 extends 基类方法,且 new 修饰符将其“隐藏”起来。这种区别在本主题中的示例显示出来。
在控制台应用程序中,声明下面的 BaseClass 和 DerivedClass 两个类. DerivedClass 继承自 BaseClass。
class BaseClass { public void Method1() { Console.WriteLine("Base - Method1"); } } class DerivedClass : BaseClass { public void Method2() { Console.WriteLine("Derived - Method2"); } }</div>
在 Main 方法中,声明变量 bc、dc 和 bcdc。
- bc 的类型为 BaseClass,并且其值的类型为 BaseClass。
- dc的类型为 DerivedClass,并且其值的类型为 DerivedClass。
- bcdc的类型为 BaseClass,并且其值的类型为 DerivedClass。这是要密切注意的变量。
由于 bc 和 bcdc 具有类型 BaseClass,因此,除非您使用强制转换,否则它们只会直接访问 Method1。变量 dc 可以访问 Method1 和 Method2。下面的代码演示这些关系。
class Program { static void Main(string[] args) { BaseClass bc = new BaseClass(); DerivedClass dc = new DerivedClass(); BaseClass bcdc = new DerivedClass(); bc.Method1(); dc.Method1(); dc.Method2(); bcdc.Method1(); } // Output: // Base - Method1 // Base - Method1 // Derived - Method2 // Base - Method1 }</div>
接下来,将以下 Method2 方法添加到 BaseClass。此方法的签名与 DerivedClass 中 Method2 方法的签名相匹配。
public void Method2() { Console.WriteLine("Base - Method2"); }</div>
由于 BaseClass 现在有 Method2 方法,因此可以为 BaseClass 变量 bc 和 bcdc 添加第二个调用语句,如下面的代码所示。
bc.Method1(); bc.Method2(); dc.Method1(); dc.Method2(); bcdc.Method1(); bcdc.Method2();</div>
当生成项目时,您将看到在 BaseClass 中添加 Method2 方法将引发警告。警告提示,DerivedClass 中的 Method2 方法将 Method2 方法隐藏在 BaseClass 中。如果要获得该结果,则建议您使用 Method2 定义中的 new 关键字。或者,可以重命名 Method2 方法之一来解决警告,但这始终不实用。
在添加 new 之前,运行该程序以查看其他调用语句生成的输出。显示以下结果。
输出:
Base - Method1 Base - Method2 Base - Method1 Derived - Method2 Base - Method1 Base - Method2</div>
new 关键字可以保留生成输出的关系,但它将取消警告。具有 BaseClass 类型的变量继续访问 BaseClass 成员,具有 DerivedClass 类型的变量首先继续访问 DerivedClass 中的成员,然后再考虑从 BaseClass 继承的成员.
要禁止显示警告,请向 Derive