Czy można zastąpić metodę niewirtualną?


92

Czy istnieje sposób na zastąpienie metody niewirtualnej? lub coś, co daje podobne wyniki (inne niż tworzenie nowej metody wywoływania żądanej metody)?

Chciałbym zastąpić metodę z Microsoft.Xna.Framework.Graphics.GraphicsDevicemając na uwadze testy jednostkowe.


8
Masz na myśli przeciążenie czy nadpisanie ? Overload = dodaj metodę o tej samej nazwie, ale różnych parametrach (np. Różne przeciążenia Console.WriteLine). Override = (w przybliżeniu) zmiana domyślnego zachowania metody (np. Metoda Shape.Draw, która ma inne zachowanie dla Circle, Rectangle, itp.). Zawsze można przeciążać metodę w klasie pochodnej, ale zastępowanie dotyczy tylko metod wirtualnych.
itowlson,

Odpowiedzi:


112

Nie, nie możesz zastąpić metody niewirtualnej. Najbliższą rzeczą, jaką możesz zrobić, jest ukrycie metody, tworząc newmetodę o tej samej nazwie, ale nie jest to zalecane, ponieważ łamie dobre zasady projektowania.

Ale nawet ukrycie metody nie zapewni polimorficznego wysyłania wywołań metod w czasie wykonywania, jak w przypadku prawdziwego wywołania metody wirtualnej. Rozważmy ten przykład:

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");
    }
}

W tym przykładzie oba wywołania Mmetody print Foo.M. Jak widać to podejście nie pozwala mieć nową implementację metody tak długo jak odniesienie do tego obiektu jest z odpowiedniego typu pochodzącego ale ukrywa metodę bazowy ma polimorfizm przerwy.

Zalecałbym, abyś nie ukrywał w ten sposób podstawowych metod.

Zwykle jestem po stronie tych, którzy preferują domyślne zachowanie C #, polegające na tym, że metody są domyślnie niewirtualne (w przeciwieństwie do Javy). Poszedłbym jeszcze dalej i powiedziałbym, że klasy powinny być również domyślnie zapieczętowane. Dziedziczenie jest trudne do prawidłowego zaprojektowania, a fakt, że istnieje metoda, która nie jest oznaczona jako wirtualna, wskazuje, że autor tej metody nigdy nie zamierzał nadpisywać metody.

Edycja: "wysyłka polimorficzna czasu wykonania" :

Rozumiem przez to domyślne zachowanie, które ma miejsce w czasie wykonywania, gdy wywołujesz metody wirtualne. Powiedzmy na przykład, że w moim poprzednim przykładzie kodu, zamiast definiować metodę niewirtualną, w rzeczywistości zdefiniowałem metodę wirtualną, a także metodę true overridden.

Gdybym miał wywołać b.Foow takim przypadku, CLR poprawnie określiłby typ obiektu, na który bwskazuje odwołanie, Bari odpowiednio wysłałby wywołanie M.


6
Chociaż „polimorficzna wysyłka czasu wykonania” jest technicznie poprawnym sposobem wyrażenia tego, wyobrażam sobie, że to prawdopodobnie przechodzi przez głowy prawie wszystkich!
Orion Edwards,

3
Chociaż prawdą jest, że autor mógł chcieć zabronić zastępowania metody, nie jest prawdą, że było to koniecznie właściwe postępowanie. Uważam, że zespół XNA powinien był zaimplementować interfejs IGraphicsDevice, aby umożliwić użytkownikowi większą elastyczność w debugowaniu i testowaniu jednostkowym. Jestem zmuszony zrobić kilka bardzo brzydkich rzeczy i zespół powinien to przewidzieć. Dalszą dyskusję można znaleźć tutaj: forums.xna.com/forums/p/30645/226222.aspx
zfedoran

2
@Orion Też tego nie rozumiem, ale po szybkim wygooglowaniu doceniłem użycie „poprawnych” terminów.
zfedoran

10
W ogóle nie kupuję argumentu „autor tego nie zamierzał”. To tak, jakby powiedzieć, że wynalazca koła nie przewidział, że koła zostaną założone w samochodach, więc nie możemy ich używać w samochodach. Wiem, jak działa koło / metoda i chcę zrobić z nią coś nieco innego. Nie obchodzi mnie, czy twórca tego chciał, czy nie. Przepraszam za próbę ożywienia martwej nici. Muszę tylko uderzenie niektórych pary zanim dowiem jakiegoś naprawdę niewygodny sposób, aby ominąć ten problem jestem w :)
Jeff

2
A co z dziedziczeniem dużej bazy kodu i potrzebą dodania testu nowej funkcjonalności, ale aby to sprawdzić, musisz utworzyć instancję wielu maszyn. W Javie po prostu rozszerzyłem te części i zastąpiłem wymagane metody kodami pośredniczącymi. W C #, jeśli metody nie są oznaczone jako wirtualne, muszę znaleźć jakiś inny mechanizm (bibliotekę mockującą itp., A nawet wtedy).
Adam Parkin,

22

Nie, nie możesz.

Możesz zastąpić tylko metodę wirtualną - zobacz MSDN tutaj :

W języku C # klasy pochodne mogą zawierać metody o tej samej nazwie, co metody klasy bazowej.

  • Metoda klasy bazowej musi być zdefiniowana jako wirtualna.

6

Jeśli klasa bazowa nie jest zapieczętowana, możesz dziedziczyć po niej i napisać nową metodę, która ukrywa podstawową (użyj słowa kluczowego „new” w deklaracji metody). W przeciwnym razie nie, nie można go zastąpić, ponieważ pierwotny autor nigdy nie zamierzał go zastąpić, dlatego nie jest wirtualny.


3

Myślę, że przeciążasz i zastępujesz zdezorientowany, przeciążenie oznacza, że ​​masz dwie lub więcej metod o tej samej nazwie, ale różne zestawy parametrów, podczas gdy zastępowanie oznacza, że ​​masz inną implementację metody w klasie pochodnej (tym samym zastępując lub modyfikując zachowanie w swojej klasie bazowej).

Jeśli metoda jest wirtualna, można ją przesłonić za pomocą słowa kluczowego override w klasie derrived. Jednak metody niewirtualne mogą ukryć podstawową implementację tylko za pomocą słowa kluczowego new zamiast słowa kluczowego override. Trasa niewirtualna jest bezużyteczna, jeśli obiekt wywołujący uzyskuje dostęp do metody za pośrednictwem zmiennej wpisanej jako typ podstawowy, ponieważ kompilator użyłby statycznej wysyłki do metody bazowej (co oznacza, że ​​kod w Twojej klasie derrived nigdy nie zostałby wywołany).

Nigdy nic nie stoi na przeszkodzie, aby dodać przeciążenie do istniejącej klasy, ale tylko kod, który wie o Twojej klasie, będzie mógł uzyskać do niej dostęp.


2

Nie można zastąpić niewirtualnej metody dowolnej klasy w C # (bez hakowania CLR), ale można zastąpić dowolną metodę interfejsu implementowaną przez klasę. Rozważ, że mamy niezamknięte

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();
    }
}

A oto demo

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();

    }
}


0

W przypadku dziedziczenia z klasy niepochodzącej, możesz po prostu utworzyć abstrakcyjną superklasę i dziedziczyć po niej niżej.


0

Czy istnieje sposób na zastąpienie metody niewirtualnej? lub coś, co daje podobne wyniki (inne niż tworzenie nowej metody wywoływania żądanej metody)?

Nie można zastąpić metody niewirtualnej. Jakkolwiek można użyć newsłowa kluczowego modyfikujący aby uzyskać podobne wyniki:

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

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

Będziesz także chciał się upewnić, że modyfikator dostępu jest również taki sam, w przeciwnym razie nie uzyskasz dziedziczenia w dół. Jeśli inny klasa dziedziczy z Class1tym newsłów kluczowych w Class1nie wpłynie obiekty dziedziczenie z nim, chyba że modyfikator dostępu jest taka sama.

Jeśli modyfikator dostępu nie jest taki sam:

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());

... a jeśli modyfikator dostępu jest taki sam:

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());

Jak wskazano w poprzedniej odpowiedzi, nie jest to dobra zasada projektowa.


-5

Można to osiągnąć za pomocą klasy abstrakcyjnej i metody abstrakcyjnej.

Rozważać

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

     void Method1()
     {
     }

     void Method2()
     {
     }

     ...
}

Teraz, jeśli chcesz mieć różne wersje metody MethodToBeTested (), zmień Class Base na klasę abstrakcyjną i metodę MethodToBeTested () jako metodę abstrakcyjną

abstract Class Base
{

     abstract void MethodToBeTested();

     void Method1()
     {
     }

     void Method2()
     {
     }

     ...
}

Z abstrakcyjnym void MethodToBeTested () pojawia się problem; wdrożenie zostało zakończone.

Stąd utwórz, class DefaultBaseImplementation : Baseaby mieć domyślną implementację.

I stwórz kolejną class UnitTestImplementation : Baseimplementację testów jednostkowych.

Te dwie nowe klasy umożliwiają przesłonięcie funkcji klas bazowych.

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

}

Class UnitTestImplementation : Base
{

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

}

Teraz masz implementację 2 klas (nadpisywanie) MethodToBeTested().

Możesz utworzyć wystąpienie (pochodnej) klasy zgodnie z wymaganiami (tj. Za pomocą implementacji podstawowej lub implementacji testów jednostkowych).


@slavoo: Cześć slavoo. Dzięki za aktualizację kodu. Ale czy mógłbyś mi podać powód odrzucenia tego?
ShivanandSK

6
Ponieważ nie odpowiada na pytanie. Pyta, czy możesz zastąpić członków, którzy nie są oznaczeni jako wirtualni. Pokazałeś, że musisz zaimplementować elementy członkowskie oznaczone jako abstrakcyjne.
Lee Louviere
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.