C#中Override关键字和New关键字的用法详解
C#语言经过专门设计,以便不同库中的基类与派生类之间的版本控制可以不断向前发展,同时保持向后兼容。这具有多方面的意义。例如,这意味着在基类中引入与派生类中的某个成员具有相同名称的新成员在C#中是完全支持的,不会导致意外行为。它还意味着类必须显式声明某方法是要重写一个继承方法,还是一个隐藏具有类似名称的继承方法的新方法。
在C#中,派生类可以包含与基类方法同名的方法。
基类方法必须定义为virtual。
- 如果派生类中的方法前面没有new或override关键字,则编译器将发出警告,该方法将有如存在new关键字一样执行操作。
- 如果派生类中的方法前面带有new关键字,则该方法被定义为独立于基类中的方法。
- 如果派生类中的方法前面带有override关键字,则派生类的对象将调用该方法,而不是调用基类方法。
可以从派生类中使用base关键字调用基类方法。
override、virtual和new关键字还可以用于属性、索引器和事件中。
默认情况下,C#方法为非虚方法。如果某个方法被声明为虚方法,则继承该方法的任何类都可以实现它自己的版本。若要使方法成为虚方法,必须在基类的方法声明中使用virtual修饰符。然后,派生类可以使用override关键字重写基虚方法,或使用new关键字隐藏基类中的虚方法。如果override关键字和new关键字均未指定,编译器将发出警告,并且派生类中的方法将隐藏基类中的方法。
为了在实践中演示上述情况,我们暂时假定公司A创建了一个名为GraphicsClass的类,您的程序将使用此类。GraphicsClass如下所示:
classGraphicsClass { publicvirtualvoidDrawLine(){} publicvirtualvoidDrawPoint(){} }
您的公司使用此类,并且您在添加新方法时将其用来派生自己的类:
classYourDerivedGraphicsClass:GraphicsClass { publicvoidDrawRectangle(){} }
您的应用程序运行正常,直到公司A发布了GraphicsClass的新版本,类似于下面的代码:
classGraphicsClass { publicvirtualvoidDrawLine(){} publicvirtualvoidDrawPoint(){} publicvirtualvoidDrawRectangle(){} }
现在,GraphicsClass的新版本中包含一个名为DrawRectangle的方法。开始时,没有出现任何问题。新版本仍然与旧版本保持二进制兼容。已经部署的任何软件都将继续正常工作,即使新类已安装到这些软件所在的计算机系统上。在您的派生类中,对方法DrawRectangle的任何现有调用将继续引用您的版本。
但是,一旦您使用GraphicsClass的新版本重新编译应用程序,就会收到来自编译器的警告CS0108。此警告提示您必须考虑希望DrawRectangle方法在应用程序中的工作方式。
如果您希望自己的方法重写新的基类方法,请使用override关键字:
classYourDerivedGraphicsClass:GraphicsClass { publicoverridevoidDrawRectangle(){} }
override关键字可确保派生自YourDerivedGraphicsClass的任何对象都将使用DrawRectangle的派生类版本。派生自YourDerivedGraphicsClass的对象仍可以使用基关键字访问DrawRectangle的基类版本:
base.DrawRectangle();
如果您不希望自己的方法重写新的基类方法,则需要注意以下事项。为了避免这两个方法之间发生混淆,可以重命名您的方法。这可能很耗费时间且容易出错,而且在某些情况下并不可行。但是,如果您的项目相对较小,则可以使用VisualStudio的重构选项来重命名方法。
或者,也可以通过在派生类定义中使用关键字new来防止出现该警告:
classYourDerivedGraphicsClass:GraphicsClass { publicnewvoidDrawRectangle(){} }
使用new关键字可告诉编译器您的定义将隐藏基类中包含的定义。这是默认行为。
重写和方法选择
当在类中指定方法时,如果有多个方法与调用兼容(例如,存在两种同名的方法,并且其参数与传递的参数兼容),则C#编译器将选择最佳方法进行调用。下面的方法将是兼容的:
publicclassDerived:Base { publicoverridevoidDoWork(intparam){} publicvoidDoWork(doubleparam){} }
在Derived的一个实例中调用DoWork时,C#编译器将首先尝试使该调用与最初在Derived上声明的DoWork版本兼容。重写方法不被视为是在类上进行声明的,而是在基类上声明的方法的新实现。仅当C#编译器无法将方法调用与Derived上的原始方法匹配时,它才尝试将该调用与具有相同名称和兼容参数的重写方法匹配。例如:
intval=5; Derivedd=newDerived(); d.DoWork(val);//CallsDoWork(double).
由于变量val可以隐式转换为double类型,因此C#编译器将调用DoWork(double),而不是DoWork(int)。有两种方法可以避免此情况。首先,避免将新方法声明为与虚方法同名。其次,可以通过将Derived的实例强制转换为Base来使C#编译器搜索基类方法列表,从而使其调用虚方法。由于是虚方法,因此将调用Derived上的DoWork(int)的实现。例如:
((Base)d).DoWork(val);//CallsDoWork(int)onDerived.
何时使用Override和New关键字
在C#中,派生类中方法的名称可与基类中方法的名称相同。可通过使用new和override关键字指定方法互动的方式。override修饰符extends基类方法,且new修饰符将其“隐藏”起来。这种区别在本主题中的示例显示出来。
在控制台应用程序中,声明下面的BaseClass和DerivedClass两个类.DerivedClass继承自BaseClass。
classBaseClass { publicvoidMethod1() { Console.WriteLine("Base-Method1"); } } classDerivedClass:BaseClass { publicvoidMethod2() { Console.WriteLine("Derived-Method2"); } }
在Main方法中,声明变量bc、dc和bcdc。
- bc的类型为BaseClass,并且其值的类型为BaseClass。
- dc的类型为DerivedClass,并且其值的类型为DerivedClass。
- bcdc的类型为BaseClass,并且其值的类型为DerivedClass。这是要密切注意的变量。
由于bc和bcdc具有类型BaseClass,因此,除非您使用强制转换,否则它们只会直接访问Method1。变量dc可以访问Method1和Method2。下面的代码演示这些关系。
classProgram { staticvoidMain(string[]args) { BaseClassbc=newBaseClass(); DerivedClassdc=newDerivedClass(); BaseClassbcdc=newDerivedClass(); bc.Method1(); dc.Method1(); dc.Method2(); bcdc.Method1(); } //Output: //Base-Method1 //Base-Method1 //Derived-Method2 //Base-Method1 }
接下来,将以下Method2方法添加到BaseClass。此方法的签名与DerivedClass中Method2方法的签名相匹配。
publicvoidMethod2() { Console.WriteLine("Base-Method2"); }
由于BaseClass现在有Method2方法,因此可以为BaseClass变量bc和bcdc添加第二个调用语句,如下面的代码所示。
bc.Method1(); bc.Method2(); dc.Method1(); dc.Method2(); bcdc.Method1(); bcdc.Method2();
当生成项目时,您将看到在BaseClass中添加Method2方法将引发警告。警告提示,DerivedClass中的Method2方法将Method2方法隐藏在BaseClass中。如果要获得该结果,则建议您使用Method2定义中的new关键字。或者,可以重命名Method2方法之一来解决警告,但这始终不实用。
在添加new之前,运行该程序以查看其他调用语句生成的输出。显示以下结果。
输出:
Base-Method1 Base-Method2 Base-Method1 Derived-Method2 Base-Method1 Base-Method2
new关键字可以保留生成输出的关系,但它将取消警告。具有BaseClass类型的变量继续访问BaseClass成员,具有DerivedClass类型的变量首先继续访问DerivedClass中的成员,然后再考虑从BaseClass继承的成员.
要禁止显示警告,请向DerivedClass中的Method2定义添加new修饰符,如下面的示例所示:可在public前后添加修饰符。
publicnewvoidMethod2() { Console.WriteLine("Derived-Method2"); }
再次运行该程序以确认没有更改输出。还确认警告不再出现。通过使用new,您断言您了解它修改的成员将隐藏从基类继承的成员。关于通过继承隐藏名称的更多信息,请参见new修饰符(C#参考)。
要将此行为与使用override的效果进行对比,请将以下方法添加到DerivedClass。可在public的前面或后面添加override修饰符。
publicoverridevoidMethod1() { Console.WriteLine("Derived-Method1"); }
将virtual修饰符添加到BaseClass中的Method1的定义。可在public的前面或后面添加virtual修饰符。
publicvirtualvoidMethod1() { Console.WriteLine("Base-Method1"); }
再次运行项目。尤其请注意下面输出的最后两行。
输出:
Base-Method1 Base-Method2 Derived-Method1 Derived-Method2 Derived-Method1 Base-Method2
使用override修饰符使bcdc能够访问DerivedClass中定义的Method1方法。通常,这是继承层次结构中所需的行为。让具有从派生类创建的值的对象使用派生类中定义的方法。通过使用override扩展基类方法可实现该行为。
下面的代码包括完整的示例。
usingSystem; usingSystem.Text; namespaceOverrideAndNew { classProgram { staticvoidMain(string[]args) { BaseClassbc=newBaseClass(); DerivedClassdc=newDerivedClass(); BaseClassbcdc=newDerivedClass(); //Thefollowingtwocallsdowhatyouwouldexpect.Theycall //themethodsthataredefinedinBaseClass. bc.Method1(); bc.Method2(); //Output: //Base-Method1 //Base-Method2 //Thefollowingtwocallsdowhatyouwouldexpect.Theycall //themethodsthataredefinedinDerivedClass. dc.Method1(); dc.Method2(); //Output: //Derived-Method1 //Derived-Method2 //Thefollowingtwocallsproducedifferentresults,depending //onwhetheroverride(Method1)ornew(Method2)isused. bcdc.Method1(); bcdc.Method2(); //Output: //Derived-Method1 //Base-Method2 } } classBaseClass { publicvirtualvoidMethod1() { Console.WriteLine("Base-Method1"); } publicvirtualvoidMethod2() { Console.WriteLine("Base-Method2"); } } classDerivedClass:BaseClass { publicoverridevoidMethod1() { Console.WriteLine("Derived-Method1"); } publicnewvoidMethod2() { Console.WriteLine("Derived-Method2"); } } }
以下示例显示了不同上下文中的类似行为。该示例定义了三个类:一个名为Car的基类,和两个由其派生的ConvertibleCar和Minivan。基类中包含DescribeCar方法。该方法给出了对一辆车的基本描述,然后调用ShowDetails来提供其他的信息。这三个类中的每一个类都定义了ShowDetails方法。new修饰符用于定义ConvertibleCar类中的ShowDetails。override修饰符用于定义Minivan类中的ShowDetails。
//Definethebaseclass,Car.Theclassdefinestwomethods, //DescribeCarandShowDetails.DescribeCarcallsShowDetails,andeachderived //classalsodefinesaShowDetailsmethod.Theexampletestswhichversionof //ShowDetailsisselected,thebaseclassmethodorthederivedclassmethod. classCar { publicvoidDescribeCar() { System.Console.WriteLine("Fourwheelsandanengine."); ShowDetails(); } publicvirtualvoidShowDetails() { System.Console.WriteLine("Standardtransportation."); } } //Definethederivedclasses. //ClassConvertibleCarusesthenewmodifiertoacknowledgethatShowDetails //hidesthebaseclassmethod. classConvertibleCar:Car { publicnewvoidShowDetails() { System.Console.WriteLine("Aroofthatopensup."); } } //ClassMinivanusestheoverridemodifiertospecifythatShowDetails //extendsthebaseclassmethod. classMinivan:Car { publicoverridevoidShowDetails() { System.Console.WriteLine("Carriessevenpeople."); } }
该示例测试被调用的ShowDetails版本。以下方法,TestCars1为每个类提供了一个实例,并在每个实例上调用DescribeCar。
publicstaticvoidTestCars1() { System.Console.WriteLine("\nTestCars1"); System.Console.WriteLine("----------"); Carcar1=newCar(); car1.DescribeCar(); System.Console.WriteLine("----------"); //Noticetheoutputfromthistestcase.Thenewmodifieris //usedinthedefinitionofShowDetailsintheConvertibleCar //class. ConvertibleCarcar2=newConvertibleCar(); car2.DescribeCar(); System.Console.WriteLine("----------"); Minivancar3=newMinivan(); car3.DescribeCar(); System.Console.WriteLine("----------"); }
TestCars1生成以下输出:尤其请注意car2的结果,该结果可能不是您所需的内容。对象的类型是ConvertibleCar,但DescribeCar不会访问ConvertibleCar中定义的ShowDetails版本,因为方法已声明包含new修饰符,而不是override修饰符。因此,ConvertibleCar对象显示与Car对象相同的说明。比较car3的结果,它是一个Minivan对象。在这种情况下,在Minivan类中声明的ShowDetails方法重写Car类中声明的ShowDetails方法,显示的说明描述微型面包车。
//TestCars1 //---------- //Fourwheelsandanengine. //Standardtransportation. //---------- //Fourwheelsandanengine. //Standardtransportation. //---------- //Fourwheelsandanengine. //Carriessevenpeople. //----------
TestCars2创建Car类型的对象列表。对象的值由Car、ConvertibleCar和Minivan类实例化而来。DescribeCar是调用列表中的每个元素。以下代码显示了TestCars2的定义。
publicstaticvoidTestCars2() { System.Console.WriteLine("\nTestCars2"); System.Console.WriteLine("----------"); varcars=newList<Car>{newCar(),newConvertibleCar(), newMinivan()}; foreach(varcarincars) { car.DescribeCar(); System.Console.WriteLine("----------"); } }
显示以下输出。请注意,此输出与由TestCars1显示的输出相同。ConvertibleCar类的ShowDetails方法不被调用,无论对象的类型是ConvertibleCar,如在TestCars1中,还是Car,如在TestCars2中。相反,car3在两种情况下都从Minivan类调用ShowDetails方法,无论它具有类型Minivan还是类型Car。
//TestCars2 //---------- //Fourwheelsandanengine. //Standardtransportation. //---------- //Fourwheelsandanengine. //Standardtransportation. //---------- //Fourwheelsandanengine. //Carriessevenpeople. //----------
完成示例的方法TestCars3和TestCars4。这些方法直接调用ShowDetails,首先从宣布具有类型ConvertibleCar和Minivan(TestCars3)的对象调用,然后从具有类型Car(TestCars4)的对象调用。以下代码定义了这两种方法。
publicstaticvoidTestCars3() { System.Console.WriteLine("\nTestCars3"); System.Console.WriteLine("----------"); ConvertibleCarcar2=newConvertibleCar(); Minivancar3=newMinivan(); car2.ShowDetails(); car3.ShowDetails(); } publicstaticvoidTestCars4() { System.Console.WriteLine("\nTestCars4"); System.Console.WriteLine("----------"); Carcar2=newConvertibleCar(); Carcar3=newMinivan(); car2.ShowDetails(); car3.ShowDetails(); }
该方法产生下面的输出,它对应本主题中第一个示例的结果。
//TestCars3 //---------- //Aroofthatopensup. //Carriessevenpeople. //TestCars4 //---------- //Standardtransportation. //Carriessevenpeople.
以下代码显示了整个项目及其输出。
usingSystem; usingSystem.Collections.Generic; usingSystem.Linq; usingSystem.Text; namespaceOverrideAndNew2 { classProgram { staticvoidMain(string[]args) { //Declareobjectsofthederivedclassesandtestwhichversion //ofShowDetailsisrun,baseorderived. TestCars1(); //Declareobjectsofthebaseclass,instantiatedwiththe //derivedclasses,andrepeatthetests. TestCars2(); //DeclareobjectsofthederivedclassesandcallShowDetails //directly. TestCars3(); //Declareobjectsofthebaseclass,instantiatedwiththe //derivedclasses,andrepeatthetests. TestCars4(); } publicstaticvoidTestCars1() { System.Console.WriteLine("\nTestCars1"); System.Console.WriteLine("----------"); Carcar1=newCar(); car1.DescribeCar(); System.Console.WriteLine("----------"); //Noticetheoutputfromthistestcase.Thenewmodifieris //usedinthedefinitionofShowDetailsintheConvertibleCar //class. ConvertibleCarcar2=newConvertibleCar(); car2.DescribeCar(); System.Console.WriteLine("----------"); Minivancar3=newMinivan(); car3.DescribeCar(); System.Console.WriteLine("----------"); }
输出:
TestCars1 ---------- Fourwheelsandanengine. Standardtransportation. ---------- Fourwheelsandanengine. Standardtransportation. ---------- Fourwheelsandanengine. Carriessevenpeople. ----------
publicstaticvoidTestCars2() { System.Console.WriteLine("\nTestCars2"); System.Console.WriteLine("----------"); varcars=newList<Car>{newCar(),newConvertibleCar(), newMinivan()}; foreach(varcarincars) { car.DescribeCar(); System.Console.WriteLine("----------"); } }
输出:
TestCars2 ---------- Fourwheelsandanengine. Standardtransportation. ---------- Fourwheelsandanengine. Standardtransportation. ---------- Fourwheelsandanengine. Carriessevenpeople. ----------
publicstaticvoidTestCars3() { System.Console.WriteLine("\nTestCars3"); System.Console.WriteLine("----------"); ConvertibleCarcar2=newConvertibleCar(); Minivancar3=newMinivan(); car2.ShowDetails(); car3.ShowDetails(); }
输出:
TestCars3 ---------- Aroofthatopensup. Carriessevenpeople.
publicstaticvoidTestCars4() { System.Console.WriteLine("\nTestCars4"); System.Console.WriteLine("----------"); Carcar2=newConvertibleCar(); Carcar3=newMinivan(); car2.ShowDetails(); car3.ShowDetails(); } //Output: //TestCars4 //---------- //Standardtransportation. //Carriessevenpeople. } //Definethebaseclass,Car.Theclassdefinestwovirtualmethods, //DescribeCarandShowDetails.DescribeCarcallsShowDetails,andeachderived //classalsodefinesaShowDetailsmethod.Theexampletestswhichversionof //ShowDetailsisused,thebaseclassmethodorthederivedclassmethod. classCar { publicvirtualvoidDescribeCar() { System.Console.WriteLine("Fourwheelsandanengine."); ShowDetails(); } publicvirtualvoidShowDetails() { System.Console.WriteLine("Standardtransportation."); } } //Definethederivedclasses. //ClassConvertibleCarusesthenewmodifiertoacknowledgethatShowDetails //hidesthebaseclassmethod. classConvertibleCar:Car { publicnewvoidShowDetails() { System.Console.WriteLine("Aroofthatopensup."); } } //ClassMinivanusestheoverridemodifiertospecifythatShowDetails //extendsthebaseclassmethod. classMinivan:Car { publicoverridevoidShowDetails() { System.Console.WriteLine("Carriessevenpeople."); } } }