Разница между виртуальным, переопределением, новым и запечатанным переопределением

79

Я довольно путать между некоторыми понятиями объектно - ориентированного программирования: virtual, override, newи sealed override. Кто-нибудь может объяснить различия?

Я совершенно ясно понимаю, что если будет использоваться метод производного класса, можно использовать overrideключевое слово, чтобы метод базового класса был переопределен производным классом. Но я не уверен new, и sealed override.

xorpower
источник

Ответы:

107

В Виртуальное ключевое слово используется для изменения метода, свойства, индексатор или событий декларации, и позволяют ему быть переопределены в производном классе. Например, этот метод может быть переопределен любым наследующим его классом: используйте модификатор new, чтобы явно скрыть член, унаследованный от базового класса. Чтобы скрыть унаследованный член, объявите его в производном классе с тем же именем и измените его с помощью модификатора new.

Все это связано с полиморфизмом. Когда виртуальный метод вызывается для ссылки, фактический тип объекта, на который ссылается ссылка, используется для решения, какую реализацию метода использовать. Когда метод базового класса переопределяется в производном классе, используется версия в производном классе, даже если вызывающий код не «знал», что объект является экземпляром производного класса. Например:

public class Base
{
  public virtual void SomeMethod()
  {
  }
}

public class Derived : Base
{
  public override void SomeMethod()
  {
  }
}

...

Base d = new Derived();
d.SomeMethod();

в конечном итоге вызовет Derived.SomeMethod, если он переопределяет Base.SomeMethod.

Теперь, если вы используете ключевое слово new вместо переопределения , метод в производном классе не переопределяет метод в базовом классе, а просто скрывает его. В этом случае используйте такой код:

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 . Использование этого модификатора предотвращает дальнейшее переопределение свойства производным классом. Аксессуары запечатанного свойства также запечатаны.

CharithJ
источник
спасибо за ввод .. но одна вещь не приходит мне в голову .. в чем польза Base b = new Derived ()? Это создающий объект базового класса или производного класса ??
xorpower
2
Производный класс. Я думаю, вам нужно больше узнать о полиморфизме. Вот хороший вариант для чтения. msdn.microsoft.com/en-us/library/ms173152(v=vs.80).aspx
CharithJ
5
@Xor: в этом случае вы создаете экземпляр Derivedобъекта и сохраняете ссылку в Baseпеременной. Это верно, потому что объект тоже Derivedявляется Baseобъектом. Это все равно что сказать, что нам нужен «человек», поэтому мы получаем «Джонни», который оказывается человеком. То же самое и здесь.
Джефф Меркадо
Я хочу добавить сюда всего один момент. Base b = new Derived()в нем говорится, что Baseкласс может быть доступен по Derived classссылке, потому что это derived classспециализация его базового класса. Derivedклассы могут выполнять все операции (например, вызывать методы базового класса и т. д. ), которые base classможет выполнять a . Но Base classне может выполнять те операции, которые он Derived classможет. Так Derived d = new Base()что неправильно, но Base b = new Derived()правильно.
mmushtaq
Можете уточнить цель использования newмодификатора hide a base class method? Во втором примере вызов b.SomeOtherMethod()вызывает реализацию базового класса (можно сказать, что он скрыл метод производного класса). Если это типичный пример использования, то, newпохоже, он используется, когда вызывающий объект намеревается иметь переменную a compile-time typeдля использования своего метода, а не метод любого, runtime typesкоторый может быть ему назначен.
Минь Тран
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вызывается Friendэкземпляром, он Friend.GetNameвызывается:

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

Когда виртуальный метод GetNameвызывается Personэкземпляром, он 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";
    }
}

Но иногда ваш друг Майк решает изменить свой пол и, следовательно, свое имя на Алиса :) Вы можете либо изменить исходный код, либо вместо этого создать подкласс Майка:

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«точки зрения s вы называете Alice.GetName, когда из Mike» s - вы называете Mike.GetName. Здесь не выполняется поиск во время выполнения - оба метода не виртуальные.

Вы всегда можете создавать newметоды - независимо от того, являются ли методы, которые вы скрываете, виртуальными или нет.

Это также относится к свойствам и событиям - они представлены как методы внизу.

Локи Криасус
источник
1
Я нигде не нашел простого и полного ответа, чем тот. Спасибо, Локи
Ривс
19

По умолчанию метод не может быть переопределен в производном классе, если он не объявлен virtual, или abstract. virtualозначает проверку наличия новых реализаций перед вызовом и abstractозначает то же самое, но гарантированно будет переопределено во всех производных классах. Кроме того, в базовом классе не требуется реализации, поскольку он будет повторно определен в другом месте.

Исключением из вышесказанного является newмодификатор. Метод не объявлен virtualили abstractможет быть переопределен с помощью newмодификатора в производном классе. Когда метод вызывается в базовом классе, выполняется базовый метод, а при вызове в производном классе выполняется новый метод. Все newключевые слова позволяют вам использовать два метода с одинаковым именем. в иерархии классов.

Наконец, sealedмодификатор прерывает цепочку virtualметодов и делает их снова недоступными для переопределения. Это не часто используется, но вариант есть. Это имеет больше смысла с цепочкой из 3 классов, каждый из которых является производным от предыдущего.

A -> B -> C

если Aесть virtualили abstractметод, то есть overriddenв B, то он также может предотвратить Cот изменения его снова, объявив его sealedв B.

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без virtualin Baseприведет к ошибке.
  3. Точно так же написание метода (без переопределения) virtualс предупреждением напишет «B» (поскольку полиморфизм не выполняется).
  4. Чтобы скрыть такое предупреждение, как в пункте выше, напишите newперед этим простым методом в Derived.
  5. new ключевое слово - это другая история, оно просто скрывает предупреждение о том, что свойство с таким же именем находится в базовом классе.
  6. virtualили newоба одинаковы, кроме нового модификатора

  7. newи overrideне может использоваться перед тем же методом или свойством.

  8. sealed перед тем, как какой-либо класс или метод заблокируют его для использования в классе Derived, и это даст ошибку времени компиляции.
Чарли
источник
Извините, но -1 из-за нескольких ошибок компиляции: метод объявлен несколько раз с одинаковыми параметрами, без кавычек вокруг строк B и D ...
DeveloperDan,