Можно ли переопределить не виртуальный метод?

92

Есть ли способ переопределить не виртуальный метод? или что-то, что дает аналогичные результаты (кроме создания нового метода для вызова желаемого метода)?

Я хотел бы переопределить метод Microsoft.Xna.Framework.Graphics.GraphicsDeviceс учетом модульного тестирования.

Zfedoran
источник
8
Вы имеете в виду перегрузку или переопределение ? Перегрузка = добавить метод с тем же именем, но с разными параметрами (например, разные перегрузки Console.WriteLine). Override = (примерно) изменить поведение метода по умолчанию (например, метод Shape.Draw, который имеет другое поведение для Circle, Rectangle и т. Д.). Вы всегда можете перегрузить метод в производном классе, но переопределение применяется только к виртуальным методам.
itowlson 05

Ответы:

112

Нет, вы не можете переопределить не виртуальный метод. Самое близкое, что вы можете сделать, - это скрыть метод, создав newметод с тем же именем, но это не рекомендуется, поскольку это нарушает принципы хорошего дизайна.

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

using System;

class Example
{
    static void Main()
    {
        Foo f = new Foo();
        f.M();

        Foo b = new Bar();
        b.M();
    }
}

class Foo
{
    public void M()
    {
        Console.WriteLine("Foo.M");
    }
}

class Bar : Foo
{
    public new void M()
    {
        Console.WriteLine("Bar.M");
    }
}

В этом примере оба вызова Mметода print Foo.M. Как вы можете видеть , этот подход действительно позволяет иметь новую реализацию метода до тех пор , как ссылка на этот объект имеет правильный производный тип , но скрывает базовый метод делает перерыв полиморфизм.

Я бы порекомендовал вам не скрывать таким образом базовые методы.

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

Изменить: «полиморфная отправка времени выполнения» :

Под этим я подразумеваю поведение по умолчанию, которое происходит во время выполнения, когда вы вызываете виртуальные методы. Скажем, например, что в моем предыдущем примере кода, вместо определения невиртуального метода, я фактически определил виртуальный метод и истинно переопределенный метод.

Если бы я позвонил b.Fooв этом случае, CLR правильно определила бы тип объекта, на который bуказывает ссылка, Barи отправила бы вызов Mсоответствующим образом.

Эндрю Хэйр
источник
6
Хотя «полиморфная отправка во время выполнения» - технически правильный способ сказать это, я полагаю, что это, вероятно, пролетает над головами почти всех!
Орион Эдвардс,
3
Хотя верно то, что автор, возможно, намеревался запретить переопределение метода, неверно, что это было обязательно правильно. Я считаю, что команде XNA следовало внедрить интерфейс IGraphicsDevice, чтобы предоставить пользователю большую гибкость при отладке и модульном тестировании. Я вынужден делать очень уродливые вещи, и команда должна была это предусмотреть. Дальнейшее обсуждение можно найти здесь: forum.xna.com/forums/p/30645/226222.aspx
zfedoran
2
@Orion Я тоже этого не понял, но после быстрого поиска в Google я оценил использование «правильных» терминов.
zfedoran 06
10
Я вообще не верю аргументу «автор этого не имел в виду». Это все равно, что сказать, что изобретатель колеса не ожидал, что колеса будут ставить на автомобили, поэтому мы не можем использовать их на автомобилях. Я знаю, как работает колесо / метод, и хочу сделать с ним что-нибудь немного другое. Меня не волнует, хотел ли меня создатель или нет. Извините за попытку оживить мертвую нить. Мне просто нужно выпустить пар, прежде чем я найду какой-нибудь действительно неудобный способ обойти эту проблему :)
Джефф
2
Так что насчет того, когда вы наследуете большую кодовую базу и вам нужно добавить тест на новые функциональные возможности, но для того, чтобы проверить это, вам нужно создать экземпляры целого ряда машин. В Java я бы просто расширил эти части и переопределил требуемые методы с помощью заглушек. В C #, если методы не помечены как виртуальные, мне нужно найти какой-то другой механизм (имитирующую библиотеку или что-то подобное, и даже тогда).
Адам Паркин
22

Нет, не можешь.

Вы можете переопределить только виртуальный метод - см. MSDN здесь :

В C # производные классы могут содержать методы с тем же именем, что и методы базового класса.

  • Метод базового класса должен быть определен виртуальным.
ChrisF
источник
6

Если базовый класс не запечатан, вы можете унаследовать от него и написать новый метод, скрывающий базовый (используйте ключевое слово «new» в объявлении метода). В противном случае нет, вы не можете переопределить его, потому что первоначальные авторы никогда не намеревались его переопределить, поэтому он не виртуальный.

слизняк
источник
3

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

Если метод виртуальный, вы можете переопределить его, используя ключевое слово override в производном классе. Однако невиртуальные методы могут скрыть базовую реализацию только с помощью ключевого слова new вместо ключевого слова override. Невиртуальный маршрут бесполезен, если вызывающий получает доступ к методу через переменную, набранную как базовый тип, поскольку компилятор будет использовать статическую отправку базовому методу (то есть код в производном классе никогда не будет вызван).

Никогда ничего не мешает вам добавить перегрузку в существующий класс, но только код, который знает о вашем классе, сможет получить к нему доступ.

Рори
источник
2

Вы не можете переопределить не виртуальный метод любого класса в C # (без взлома CLR), но вы можете переопределить любой метод интерфейса, который реализует класс. Считайте, что у нас негерметичный

class GraphicsDevice: IGraphicsDevice {
    public void DoWork() {
        Console.WriteLine("GraphicsDevice.DoWork()");
    }
}

// with its interface
interface IGraphicsDevice {
    void DoWork();
}

// You can't just override DoWork in a child class,
// but if you replace usage of GraphicsDevice to IGraphicsDevice,
// then you can override this method (and, actually, the whole interface).

class MyDevice: GraphicsDevice, IGraphicsDevice {
    public new void DoWork() {
        Console.WriteLine("MyDevice.DoWork()");
        base.DoWork();
    }
}

А вот демо

class Program {
    static void Main(string[] args) {

        IGraphicsDevice real = new GraphicsDevice();
        var myObj = new MyDevice();

        // demo that interface override works
        GraphicsDevice myCastedToBase = myObj;
        IGraphicsDevice my = myCastedToBase;

        // obvious
        Console.WriteLine("Using real GraphicsDevice:");
        real.DoWork();

        // override
        Console.WriteLine("Using overriden GraphicsDevice:");
        my.DoWork();

    }
}
Вячеслав Нападовский
источник
@Dan, вот живая демонстрация: dotnetfiddle.net/VgRwKK
Вячеслав Нападовский,
0

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

user3853059
источник
0

Есть ли способ переопределить не виртуальный метод? или что-то, что дает аналогичные результаты (кроме создания нового метода для вызова желаемого метода)?

Вы не можете переопределить не виртуальный метод. Однако вы можете использовать newключевое слово-модификатор для получения аналогичных результатов:

class Class0
{
    public int Test()
    {
        return 0;
    }
}

class Class1 : Class0
{
    public new int Test()
    {
        return 1;
    }
}
. . .
// result of 1
Console.WriteLine(new Class1().Test());

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

Если модификатор доступа не тот:

class Class0
{
    protected int Test()
    {
        return 0;
    }
}

class Class1 : Class0
{
    // different access modifier
    new int Test()
    {
        return 1;
    }
}

class Class2 : Class1
{
    public int Result()
    {
        return Test();
    }
}
. . .
// result of 0
Console.WriteLine(new Class2().Result());

... против , если модификатор доступа является то же самое:

class Class0
{
    protected int Test()
    {
        return 0;
    }
}

class Class1 : Class0
{
    // same access modifier
    protected new int Test()
    {
        return 1;
    }
}

class Class2 : Class1
{
    public int Result()
    {
        return Test();
    }
}
. . .
// result of 1
Console.WriteLine(new Class2().Result());

Как указывалось в предыдущем ответе, это не лучший принцип дизайна.

Аарон Томас
источник
-5

Есть способ добиться этого, используя абстрактный класс и абстрактный метод.

Рассмотреть возможность

Class Base
{
     void MethodToBeTested()
     {
        ...
     }

     void Method1()
     {
     }

     void Method2()
     {
     }

     ...
}

Теперь, если вы хотите иметь разные версии метода MethodToBeTested (), измените Class Base на абстрактный класс и метод MethodToBeTested () как абстрактный метод.

abstract Class Base
{

     abstract void MethodToBeTested();

     void Method1()
     {
     }

     void Method2()
     {
     }

     ...
}

С абстрактным void MethodToBeTested () возникает проблема; реализация ушла.

Следовательно, создайте, class DefaultBaseImplementation : Baseчтобы иметь реализацию по умолчанию.

И создайте еще один, class UnitTestImplementation : Baseчтобы иметь реализацию модульного теста.

С помощью этих двух новых классов можно переопределить функциональность базового класса.

Class DefaultBaseImplementation : Base    
{
    override void MethodToBeTested()    
    {    
        //Base (default) implementation goes here    
    }

}

Class UnitTestImplementation : Base
{

    override void MethodToBeTested()    
    {    
        //Unit test implementation goes here    
    }

}

Теперь у вас есть 2 класса, реализующие (переопределяющие) MethodToBeTested() .

Вы можете создать экземпляр (производного) класса по мере необходимости (т.е. либо с базовой реализацией, либо с реализацией модульного теста).

ШиванандСК
источник
@slavoo: Привет, славоо. Спасибо за обновление кода. Но не могли бы вы рассказать мне причину отказа?
ShivanandSK
6
Потому что это не отвечает на вопрос. Он спрашивает, можете ли вы переопределить участников, не помеченных как виртуальные. Вы продемонстрировали, что вам необходимо реализовать элементы, отмеченные как абстрактные.
Ли Лувьер