【发布时间】:2011-09-03 23:35:08
【问题描述】:
我对 OOP 的一些概念感到很困惑:virtual、override、new 和 sealed override。谁能解释一下区别?
我很清楚,如果要使用派生类方法,可以使用override 关键字,这样基类方法将被派生类覆盖。但我不确定new 和sealed override。
【问题讨论】:
标签: c# oop overriding virtual new-operator
我对 OOP 的一些概念感到很困惑:virtual、override、new 和 sealed override。谁能解释一下区别?
我很清楚,如果要使用派生类方法,可以使用override 关键字,这样基类方法将被派生类覆盖。但我不确定new 和sealed override。
【问题讨论】:
标签: c# oop overriding virtual new-operator
任何方法都可以被覆盖(=virtual)。由定义方法的人做出决定:
class Person
{
// this one is not overridable (not virtual)
public String GetPersonType()
{
return "person";
}
// this one is overridable (virtual)
public virtual String GetName()
{
return "generic name";
}
}
现在您可以覆盖那些可覆盖的方法:
class Friend : Person
{
public Friend() : this("generic name") { }
public Friend(String name)
{
this._name = name;
}
// override Person.GetName:
public override String GetName()
{
return _name;
}
}
但是您不能覆盖 GetPersonType 方法,因为它不是虚拟的。
让我们创建这些类的两个实例:
Person person = new Person();
Friend friend = new Friend("Onotole");
当Friend 实例调用非虚拟方法GetPersonType 时,实际上调用的是Person.GetPersonType:
Console.WriteLine(friend.GetPersonType()); // "person"
当Friend 实例调用虚拟方法GetName 时,调用的是Friend.GetName:
Console.WriteLine(friend.GetName()); // "Onotole"
当Person 实例调用虚拟方法GetName 时,调用的是Person.GetName:
Console.WriteLine(person.GetName()); // "generic name"
当调用非虚方法时,方法体不会被查找——编译器已经知道需要调用的实际方法。而对于虚拟方法,编译器无法确定调用哪一个,并且在运行时在类层次结构中从下到上从调用该方法的实例类型开始查找:对于friend.GetName,它看起来开始在Friend 类并立即找到它,对于person.GetName 类它从Person 开始并在那里找到它。
有时您创建一个子类,覆盖一个虚拟方法,并且您不希望在层次结构中再进行任何覆盖 - 为此您使用 sealed override(说您是最后一个覆盖该方法的人):
class Mike : Friend
{
public sealed override String GetName()
{
return "Mike";
}
}
但有时您的朋友 Mike 决定更改他的性别,因此将他的名字改为 Alice :) 您可以更改原始代码或改为子类 Mike:
class Alice : Mike
{
public new String GetName()
{
return "Alice";
}
}
在这里,您创建了一个完全不同的具有相同名称的方法(现在您有两个)。调用哪种方法以及何时调用?这取决于你如何称呼它:
Alice alice = new Alice();
Console.WriteLine(alice.GetName()); // the new method is called, printing "Alice"
Console.WriteLine(((Mike)alice).GetName()); // the method hidden by new is called, printing "Mike"
当你从Alice 的角度调用它时,你调用Alice.GetName,当你从Mike 的角度调用它时——你调用Mike.GetName。这里没有进行运行时查找 - 因为这两种方法都是非虚拟的。
您始终可以创建 new 方法 - 无论您隐藏的方法是否为虚拟方法。
这也适用于属性和事件 - 它们在下面表示为方法。
【讨论】:
public class Base
{
public virtual void SomeMethod()
{
Console.WriteLine("B");
}
}
public class Derived : Base
{
//Same method is written 3 times with different keywords to explain different behaviors.
//This one is Simple method
public void SomeMethod()
{
Console.WriteLine("D");
}
//This method has 'new' keyword
public new void SomeMethod()
{
Console.WriteLine("D");
}
//This method has 'override' keyword
public override void SomeMethod()
{
Console.WriteLine("D");
}
}
现在是第一件事
Base b=new Base();
Derived d=new Derived();
b.SomeMethod(); //will always write B
d.SomeMethod(); //will always write D
现在关键字都是关于多态性的
Base b = new Derived();
virtual 并在 Derived 中覆盖将得到 D(多态性)。Base 中使用override 而不使用virtual 会出错。virtual 编写方法(无覆盖)将写入带有警告的“B”(因为没有进行多态性)。Derived 中的那个简单方法之前写new。new 关键字是另一回事,它只是隐藏了警告,告知基类中存在同名属性。virtual 或 new 两者都相同,除了
new modifier
new 和 override 不能在相同的方法或属性之前使用。
sealed 在任何类或方法将其锁定以在派生类中使用之前,它会给出编译时错误。【讨论】:
virtual 关键字用于修改方法、属性、索引器或事件声明,并允许在派生类中对其进行覆盖。例如,这个方法可以被任何继承它的类覆盖: 使用 new 修饰符显式隐藏从基类继承的成员。要隐藏继承的成员,请在派生类中使用相同的名称声明它,并使用 new 修饰符对其进行修改。
这与多态性有关。当在引用上调用虚方法时,引用所引用的对象的实际类型用于决定使用哪个方法实现。在派生类中重写基类的方法时,将使用派生类中的版本,即使调用代码不“知道”该对象是派生类的实例。例如:
public class Base
{
public virtual void SomeMethod()
{
}
}
public class Derived : Base
{
public override void SomeMethod()
{
}
}
...
Base d = new Derived();
d.SomeMethod();
如果覆盖 Base.SomeMethod,最终将调用 Derived.SomeMethod。
现在,如果你使用 new 关键字代替 override,派生类中的方法不会覆盖基类中的方法,它只是隐藏它.在这种情况下,代码如下:
public class Base
{
public virtual void SomeOtherMethod()
{
}
}
public class Derived : Base
{
public new void SomeOtherMethod()
{
}
}
...
Base b = new Derived();
Derived d = new Derived();
b.SomeOtherMethod();
d.SomeOtherMethod();
将首先调用 Base.SomeOtherMethod ,然后是 Derived.SomeOtherMethod 。它们实际上是两个完全独立的方法,它们恰好具有相同的名称,而不是覆盖基方法的派生方法。
如果您没有指定 new 或 overrides,则结果输出与您指定 new 时相同,但您也会收到编译器警告(因为您可能不知道您在基类方法,或者实际上您可能想要覆盖它,只是忘记包含关键字)。
覆盖属性声明可能包含 sealed 修饰符。使用此修饰符可防止派生类进一步覆盖该属性。密封属性的访问者也是密封的。
【讨论】:
Derived 对象的实例并将引用存储在Base 变量中。这是有效的,因为Derived 对象也是Base 对象。这就像说我们需要一个“人”,所以我们得到“约翰尼”,他恰好是一个人。同样的交易。
Base b = new Derived() 它声明 Base 类可以通过 Derived class 引用访问,因为 derived class 是其基类的特化。 Derived 类可以执行base class 可以执行的所有操作(例如调用基类方法等)。但是Base class 无法执行其Derived class 可以执行的操作。所以Derived d = new Base() 不正确,但Base b = new Derived() 是正确的。
new修饰符到hide a base class method的目的吗?在第二个示例中,调用b.SomeOtherMethod() 调用基类实现(有人可能会说它隐藏派生类的方法)。如果这是一个典型的用法示例,那么当调用者打算使用 compile-time type 的变量来使用其方法时,似乎使用了 new,而不是可能分配给它的任何 runtime types 的方法。
默认情况下,除非声明为virtual 或abstract,否则不能在派生类中重写方法。 virtual 表示在调用之前检查更新的实现,abstract 表示相同,但保证在所有派生类中都会被覆盖。此外,基类中不需要实现,因为它将在其他地方重新定义。
上述情况的例外是new 修饰符。未声明 virtual 或 abstract 的方法可以在派生类中使用 new 修饰符重新定义。在基类中调用方法时执行基方法,在派生类中调用时执行新方法。所有new 关键字允许您做的就是在一个类层次结构中有两个方法同名。
最后,sealed 修饰符打破了virtual 方法链,使它们不再被覆盖。这不经常使用,但选项在那里。使用 3 个类的链更有意义,每个类都派生自前一个类
A -> B -> C
如果A有一个virtual或abstract方法,即B中的overridden,那么它还可以通过在B中声明sealed来防止C再次更改它.
sealed 也用于classes,这是您经常会遇到此关键字的地方。
我希望这会有所帮助。
【讨论】: