虚拟,替代,新替代和密封替代之间的区别


79

我很困惑OOP的一些概念之间:virtualoverridenewsealed override。谁能解释差异?

我很清楚,如果要使用派生类方法,则可以使用override关键字,这样派生类将覆盖基类方法。但是我不确定new,和sealed override

Answers:


107

虚拟关键字用于修改的方法,属性,索引或事件声明,并允许它被覆盖在派生类。例如,此方法可以被任何继承该方法的类覆盖:使用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也不重写,则结果输出与指定new相同,但是还会收到编译器警告(因为您可能不知道自己在基类中隐藏了一个方法方法,或者实际上您可能想覆盖它,而只是忘记了包含关键字)。

覆盖的属性声明可以包含密封的修饰符。使用此修饰符可防止派生类进一步覆盖该属性。具有密封特性的访问器也被密封。


感谢您的输入..但是我没有想到的是.. Base b = new Derived()的用途是什么?这是基类还是派生类的创建对象?
xorpower 2011年

2
派生类。我认为您必须更多地研究多态性。这是供您阅读的好书。msdn.microsoft.com/zh-CN/library/ms173152(v=vs.80).aspx
CharithJ,2011年

5
@Xor:在这种情况下,您正在创建Derived对象的实例,并将引用存储在Base变量中。这是有效的,因为Derived对象也是-Base对象。这就像在说我们需要一个“人”,以便我们碰巧是一个人的“约翰尼”。同样的交易。
杰夫·梅卡多

我只想在此加一点。Base b = new Derived()它声明一个Base类可以通过Derived class引用来访问,因为aderived class是其基类的专门化。Derived类可以执行a可以执行的所有操作(例如,调用基类方法等base class。但是Base class无法执行其Derived class可以执行的操作。因此,这Derived d = new Base()是不正确的,但是Base b = new Derived()正确的。
mmushtaq,2016年

您能否阐明使用new修饰符的目的hide a base class method?在第二个示例中,该调用b.SomeOtherMethod()调用基类实现(可能会说它已经隐藏了派生类的方法)。如果这是用法的典型示例,则new当调用者打算使用a的变量compile-time type来使用其方法而不是runtime types可能分配给它的任何方法时,似乎会使用它。
Minh Tran,

35

任何方法都可以重写(= 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");

当实例GetPersonType调用非虚拟方法时Fiend,实际上Person.GetPersonType就是这样:

Console.WriteLine(friend.GetPersonType()); // "person"

当实例GetName调用虚拟方法时,即为:FriendFriend.GetName

Console.WriteLine(friend.GetName()); // "Onotole"

当实例GetName调用虚拟方法时,即为:PersonPerson.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方法-所隐藏的方法是否是虚拟的。

这也适用于属性和事件-它们表示为下面的方法。


1
没有比我在任何地方都能找到的简单而完整的答案了。感谢Loki
Reevs

19

默认情况下,除非声明了方法virtual,否则不能在派生类中重写方法abstractvirtual表示在调用之前检查较新的实现,并且abstract表示相同,但​​是可以保证在所有派生类中将其重写。同样,基类不需要实现,因为它将在其他地方重新定义。

上面的例外是new修饰符。未声明virtualabstract可以使用new派生类中的修饰符重新定义的方法。在基类中调用该方法时,将执行该基方法;在派生类中调用该方法时,将执行新方法。new您所要做的所有关键字就是在类层次结构中使用两个具有相同名称的方法。

最终,sealed修饰符打破了virtual方法链,并使它们不再可重写。该选项不经常使用,但是可以使用。将3个类别的链从上一个衍生而来,这更有意义

A -> B -> C

如果A具有in中的virtualorabstract方法,则还可以通过在中声明它来防止再次更改它。overriddenBCsealedB

sealed也用于中classes,这是您通常会遇到此关键字的地方。

我希望这有帮助。


8
 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();
  1. virtual在基类中使用并在中重写Derived将得到D(多态性)。
  2. override不使用virtualinBase将产生错误。
  3. 类似地,使用编写方法(无覆盖)时,virtual将写有警告的“ B”(因为未完成多态性)。
  4. 要隐藏上述警告,请在中使用new该简单方法Derived
  5. new 关键字是另一回事,它只是隐藏警告,告诉您基类中存在具有相同名称的属性。
  6. virtualnew新修饰符外,两者都相同

  7. new并且override不能在相同的方法或属性之前使用。

  8. sealed 在任何类或方法将其锁定以在派生类中使用之前,它会给出编译时错误。

抱歉,但由于多个编译错误而为-1:方法使用相同的参数声明了多次,在字符串B和D周围没有引号...
DeveloperDan
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.