Чи можна замінити невіртуальний метод?


92

Чи є спосіб замінити невіртуальний метод? або щось, що дає подібні результати (крім створення нового методу для виклику бажаного методу)?

Я хотів би замінити метод, Microsoft.Xna.Framework.Graphics.GraphicsDeviceмаючи на увазі модульне тестування.


8
Ви маєте на увазі перевантаження чи перевизначення ? Перевантаження = додайте метод з однаковим іменем, але з різними параметрами (наприклад, різні перевантаження Console.WriteLine). Замінити = (приблизно) змінити поведінку методу за замовчуванням (наприклад, метод Shape.Draw, який має іншу поведінку для кола, прямокутника тощо). Ви завжди можете перевантажити метод у похідному класі, але перевизначення стосується лише віртуальних методів.
itowlson

Відповіді:


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, щоб дозволити користувачеві більше гнучкості при налагодженні та модульному тестуванні. Я змушений робити дуже потворні речі, і це повинно було передбачити команда. Подальше обговорення можна знайти тут: forums.xna.com/forums/p/30645/226222.aspx
zfedoran

2
@Orion Я теж цього не зрозумів, але після швидкого google я вдячний, побачивши використання "правильних" термінів.
zfedoran

10
Я взагалі не купую аргумент "автор не мав на меті". Це все одно, що сказати, що винахідник колеса не передбачав, що колеса ставлять на машини, тому ми не можемо використовувати їх на автомобілях. Я знаю, як працює колесо / метод, і хочу з ним зробити щось дещо інше. Мені байдуже, хотів це творець чи ні. Вибачте за спробу оживити мертву нитку. Мені просто потрібно трохи подути, перш ніж я зрозумію якийсь незручний спосіб обійти цю проблему, в якій я переживаю :)
Джефф,

2
Отже, що стосується випадків, коли ви успадковуєте велику базу коду і вам потрібно додати тест на нові функції, але для того, щоб перевірити, що вам потрібно створити екземпляр цілого ряду механізмів. У Java я просто розширював ці частини і замінював методи, необхідні для заглушок. У C #, якщо методи не позначені як віртуальні, я маю знайти якийсь інший механізм (насмішкова бібліотека чи інший, і навіть тоді).
Адам Паркін,

22

Ні, ти не можеш.

Ви можете замінити лише віртуальний метод - див. MSDN тут :

У C # похідні класи можуть містити методи з тим самим іменем, що і методи базового класу.

  • Метод базового класу повинен бути визначений віртуальним.

6

Якщо базовий клас не запечатаний, тоді ви можете успадкувати його та написати новий метод, який приховує базовий (використовуйте ключове слово "new" у декларації методу). В іншому випадку ні, ви не можете його замінити, оскільки оригінальні автори ніколи не прагнули його замінити, отже, чому він не є віртуальним.


3

Я думаю, що ви перевантажуєтесь і перевизначаєте плутанину, перевантаження означає, що у вас є два або більше методів з однаковим іменем, але різні набори параметрів, а перевизначення означає, що у вас є інша реалізація методу у похідному класі (тим самим замінюючи або змінюючи поведінку в базовому класі).

Якщо метод віртуальний, ви можете замінити його, використовуючи ключове слово override у виведеному класі. Однак невіртуальні методи можуть приховати лише базову реалізацію, використовуючи нове ключове слово замість ключового слова 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

У випадку, якщо ви успадковуєте від непохідного класу, ви можете просто створити абстрактний супер клас і натомість успадкувати його від нього.


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 () виникає проблема; впровадження зникло.

Отже, створити a, class DefaultBaseImplementation : Baseщоб мати реалізацію за замовчуванням.

І створіть інший, class UnitTestImplementation : Baseщоб мати реалізацію модульного тесту.

За допомогою цих 2 нових класів функціональність базового класу може бути замінена.

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
Тому що це не відповідає на питання. Він запитує, чи можете ви замінити учасників, не позначених як віртуальні. Ви продемонстрували, що вам доводиться застосовувати членів, позначених абстрактними.
Lee Louviere
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.