Дизайн С#: Почему новый или переопределяемый требуется для абстрактных методов, но не для виртуальных методов?

Почему для абстрактных методов требуется новое/переопределенное, но не виртуальные методы?

Пример 1:

abstract class ShapesClass
{
    abstract public int Area(); // abstract!
}

class Square : ShapesClass
{
    int x, y;

    public int Area() // Error: missing 'override' or 'new'
    {
        return x * y;
    }
}

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

Пример 2:

class ShapesClass
{
    virtual public int Area() { return 0; } // it is virtual now!
}

class Square : ShapesClass
{
    int x, y;

    public int Area() // no explicit 'override' or 'new' required
    {
        return x * y;
    }
}

Это будет компилироваться отлично, скрыв метод по умолчанию.

Я полностью понимаю технические различия. Однако мне интересно, почему язык был разработан именно так. Не лучше ли было бы иметь такое же ограничение в "Образце 2"? В большинстве случаев, если вы создаете метод с тем же именем, что и в родительском классе, вы обычно намерены его переопределить. Поэтому я думаю, что явное указание Override/New имеет смысл и для виртуальных методов.

Существует ли конструктивная причина такого поведения?

Update: Второй пример фактически вызывает предупреждение. Первый пример показывает ошибку, потому что подкласс необходим для реализации абстрактного метода. Я не видел предупреждения в VS.. теперь имеет для меня смысл. Спасибо.

Ответы

Ответ 1

Используя либо компилятор С# 3.0, поставляемый в .NET 3.5 SP1, либо компилятор С# 4.0, поставляемый в .NET 4.0, я получаю следующую ошибку для вашего первого примера:

ошибка CS0534: "ConsoleApplication3.Square" не реализует унаследованный абстрактный элемент "ConsoleApplication3.ShapesClass.Area()"

И следующее предупреждение для второго:

предупреждение CS0114: 'ConsoleApplication3.Square.Area()' скрывает унаследованный элемент 'ConsoleApplication3.ShapesClass.Area()'. Чтобы заставить текущий элемент переопределить эту реализацию, добавьте ключевое слово переопределения. В противном случае добавьте новое ключевое слово.

В первом случае это ошибка, потому что вы на самом деле не переопределяете базовый метод, а это означает, что для абстрактного метода в конкретном классе не существует реализации. Во втором случае это предупреждение, потому что код технически корректен, но компилятор подозревает, что это не то, что вы имели в виду. Это одна из причин, по которой обычно рекомендуется включить настройку компиляции "обрабатывать предупреждения как ошибки".

Поэтому я не могу воспроизвести ваше поведение, и поведение компилятора выглядит правильно для меня. Какую версию компилятора вы используете?

Ответ 2

Здесь ответ прямо из спецификации С#.

... скрытие доступного имени из унаследованная область вызывает предупреждение сообщили. В примере

class Base
{
    public void F() {}
}
class Derived: Base
{
    public void F() {}      // Warning, hiding an inherited name
}

объявление F в Производных причинах предупреждение, о котором следует сообщать. Скрытие унаследованное имя конкретно не является ошибки, поскольку это исключало бы отдельная эволюция базовых классов. Например, вышеупомянутая ситуация может возникли потому, что позже версия Base вводила метод F это не было в предыдущем версия класса. Если бы вышеупомянутое ситуация была ошибкой, тогда любая изменение, внесенное в базовый класс в отдельная версия библиотеки классов может потенциально классы становятся недействительными. Предупреждение вызванное скрытием унаследованного имени, может быть устранены путем использования нового Модификатор:

class Base
{
    public void F() {}
}
class Derived: Base
{
    new public void F() {}
}

Новый модификатор указывает, что F в Derived является "новым", и что это действительно предназначалось для скрытия унаследованных член.

Ответ 3

Разница заключается в том, что абстрактный метод должен быть переопределен, но виртуальный не работает.

Это ошибка наследования абстрактного класса (в не-абстрактном классе) без реализации всех абстрактных членов, но вы получаете предупреждение только при наследовании от класса без указания override или new для виртуального метода.

Ответ 4

Я думаю, что в первую очередь вы получаете ошибку компилятора, потому что абстрактный метод скрыт и не реализован (так эффективно ваш класс ошибается, и было бы трудно понять, почему). Во втором случае вы получаете только предупреждение, потому что класс полезен.

Ответ 5

Versioning

На первый взгляд вы можете подумать, что скрытие метода не кажется особенно полезным. Есть ситуация где вам может понадобиться использовать его. Допустим, вы хотите использовать класс с именем MotorVehicle, который был написан другим программистом, и вы хотите использовать этот класс для получения собственного класса. В дальнейшем, позволяет также предположить, что вы хотите определить метод Accelerate() в производном классе. Например:

public class Car : MotorVehicle
{
    // define the Accelerate() method
    public void Accelerate()
    {
        Console.WriteLine("In Car Accelerate() method");
        Console.WriteLine(model + " accelerating");
    }
}

Далее, давайте предположим, что другой программист позже модифицирует класс MotorVehicle и решает добавить свой собственный метод virtual Accelerate():

public class MotorVehicle
{
    // define the Accelerate() method
    public virtual void Accelerate()
    {
        Console.WriteLine("In MotorVehicle Accelerate() method");
        Console.WriteLine(model + " accelerating");
    }
}

Добавление этого метода Accelerate() другим программистом вызывает проблему: Accelerate() метод в вашем классе Car скрывает унаследованный метод Accelerate(), определенный в их класс MotorVehicle. Позже, когда вы приступите к компиляции своего класса Car, это не ясно для компилятора действительно ли вы намеревались использовать ваш метод, чтобы скрыть унаследованный метод. Из-за этого компилятор сообщает следующее предупреждение, когда вы пытаетесь скомпилировать свой класс Car:

предупреждение CS0114: "Car.Accelerate() скрывает унаследованный элемент" MotorVehicle.Accelerate(). Чтобы текущий член переопределил этот реализации, добавьте ключевое слово переопределения. В противном случае добавьте новое ключевое слово.