Dlaczego nie mogę mieć abstrakcyjnych metod statycznych w C #?

182

Ostatnio współpracuję z dostawcami i natknąłem się na interesującą sytuację, w której chciałem mieć klasę abstrakcyjną z abstrakcyjną metodą statyczną. Przeczytałem kilka postów na ten temat i ma to sens, ale czy istnieje jakieś przyjemne, jasne wytłumaczenie?

lomaxx
źródło
3
Pozostaw je otwarte, aby umożliwić przyszłe ulepszenia.
Mark Biek
6
Myślę, że pytanie polega na tym, że C # potrzebuje innego słowa kluczowego, właśnie w takiej sytuacji. Chcesz metody, której zwracana wartość zależy tylko od typu, na który jest wywoływana. Nie można nazwać go „statycznym”, jeśli wspomniany typ jest nieznany. Ale gdy typ zostanie poznany, stanie się statyczny. „Nierozstrzygnięty statyczny” to pomysł - jeszcze nie jest statyczny, ale kiedy poznamy typ odbiornika, będzie. Jest to idealna koncepcja, dlatego programiści wciąż o nią proszą. Nie pasowało to jednak do sposobu, w jaki projektanci myśleli o języku.
William Jockusch
@WilliamJockusch co oznacza typ odbioru? Jeśli wywołam BaseClass.StaticMethod (), wówczas BaseClass jest jedynym typem, którego może użyć do podjęcia decyzji. Ale na tym poziomie jest abstrakcyjny, więc nie można rozwiązać tej metody. Jeśli zamiast tego dobrze wywołasz DerivedClass.StaticMethod, klasa podstawowa nie ma znaczenia.
Martin Capodici,
W klasie podstawowej metoda jest nierozwiązana i nie można jej użyć. Potrzebujesz typu pochodnego lub obiektu (który z kolei miałby typ pochodny). Powinieneś być w stanie wywołać baseClassObject.Method () lub DerivedClass.Method (). Nie można wywołać funkcji BaseClass.Method (), ponieważ nie daje to typu.
William Jockusch
Możliwy duplikat Jak zaimplementować wirtualne właściwości statyczne?
peterh - Przywróć Monikę

Odpowiedzi:

157

Metody statyczne nie są tworzone jako takie, są one po prostu dostępne bez odwołania do obiektu.

Wywołanie metody statycznej odbywa się poprzez nazwę klasy, a nie przez odwołanie do obiektu, a kod języka pośredniego (IL), aby ją wywołać, wywoła metodę abstrakcyjną poprzez nazwę klasy, która ją zdefiniowała, niekoniecznie nazwę klasa, której użyłeś.

Pokażę przykład.

Z następującym kodem:

public class A
{
    public static void Test()
    {
    }
}

public class B : A
{
}

Jeśli zadzwonisz do B.Test, wykonaj następujące czynności:

class Program
{
    static void Main(string[] args)
    {
        B.Test();
    }
}

Zatem rzeczywisty kod w metodzie Main wygląda następująco:

.entrypoint
.maxstack 8
L0000: nop 
L0001: call void ConsoleApplication1.A::Test()
L0006: nop 
L0007: ret 

Jak widać, wywoływane jest A.Test, ponieważ to klasa A go zdefiniowała, a nie B.Test, nawet jeśli można w ten sposób napisać kod.

Gdybyś miał typy klas , takie jak w Delphi, w których możesz utworzyć zmienną odnoszącą się do typu, a nie obiektu, miałbyś więcej zastosowania dla wirtualnych, a tym samym abstrakcyjnych metod statycznych (a także konstruktorów), ale nie są one dostępne i dlatego wywołania statyczne nie są wirtualne w .NET.

Zdaję sobie sprawę, że projektanci IL mogliby umożliwić kompilację kodu w celu wywołania B.Test i rozwiązania wywołania w czasie wykonywania, ale nadal nie byłby wirtualny, ponieważ nadal musiałbyś tam napisać jakąś nazwę klasy.

Metody wirtualne, a tym samym abstrakcyjne, są użyteczne tylko wtedy, gdy używasz zmiennej, która w czasie wykonywania może zawierać wiele różnych typów obiektów, a zatem chcesz wywołać odpowiednią metodę dla bieżącego obiektu, który masz w zmiennej. W przypadku metod statycznych i tak musisz przejść przez nazwę klasy, więc dokładna metoda do wywołania jest znana w czasie kompilacji, ponieważ nie może i nie zmieni się.

Dlatego wirtualne / abstrakcyjne metody statyczne nie są dostępne w .NET.

Lasse V. Karlsen
źródło
4
W połączeniu ze sposobem, w jaki przeciążanie operatora odbywa się w języku C #, niestety eliminuje to możliwość wymagania od podklas zapewnienia implementacji dla przeciążenia danego operatora.
Chris Moschini,
23
Nie uważam tej odpowiedzi za szczególnie przydatną, ponieważ definicja Test()jest Araczej abstrakcyjna i potencjalnie zdefiniowana w B. \
5
Ogólne parametry typu skutecznie zachowują się jak nietrwałe zmienne typu, a wirtualne metody statyczne mogą być przydatne w takim kontekście. Na przykład, jeśli ktoś ma Cartyp z wirtualną statyczną CreateFromDescriptionmetodą fabryczną, wówczas kod, który akceptuje Cartyp ogólny ograniczony, Tmógłby wywołać T.CreateFromDescriptionsamochód typu T. Taki konstrukt mógłby być obsługiwany całkiem dobrze w CLR, gdyby każdy typ, który zdefiniował taką metodę, posiadał statyczną instancję singleton klasy ogólnej klasy zagnieżdżonej, która zawierała wirtualne metody „statyczne”.
supercat
45

Metody statyczne nie mogą być dziedziczone ani zastępowane, dlatego nie mogą być abstrakcyjne. Ponieważ metody statyczne są zdefiniowane na typie, a nie na instancji klasy, muszą być jawnie wywoływane dla tego typu. Więc jeśli chcesz wywołać metodę na klasie potomnej, musisz użyć jej nazwy, aby ją wywołać. To sprawia, że ​​dziedziczenie jest nieistotne.

Załóżmy, że możesz na chwilę odziedziczyć metody statyczne. Wyobraź sobie ten scenariusz:

public static class Base
{
    public static virtual int GetNumber() { return 5; }
}

public static class Child1 : Base
{
    public static override int GetNumber() { return 1; }
}

public static class Child2 : Base
{
    public static override int GetNumber() { return 2; }
}

Jeśli wywołasz funkcję Base.GetNumber (), która metoda zostanie wywołana? Która wartość zwrócona? Bardzo łatwo zauważyć, że bez tworzenia instancji obiektów dziedziczenie jest raczej trudne. Metody abstrakcyjne bez dziedziczenia to tylko metody, które nie mają ciała, więc nie można ich nazwać.

David Wengier
źródło
33
Biorąc pod uwagę twój scenariusz powiedziałbym, że Base.GetNumber () zwróci 5; Child1.GetNumber () zwraca 1; Child2.GetNumber () zwraca 2; Czy możesz udowodnić, że się mylę, aby pomóc mi zrozumieć twoje rozumowanie? Dziękuję
Luis Filipe
Twarz, którą Twoim zdaniem Base.GetNumber () zwraca 5, oznacza, że ​​już rozumiesz, co się dzieje. Zwracając wartość podstawową, nie następuje dziedziczenie.
David Wengier
60
Dlaczego, na świecie, funkcja Base.GetNumber () zwraca wszystko inne niż 5? Jest to metoda w klasie bazowej - istnieje tylko jedna opcja.
Artem Russakovskii
4
@ArtemRussakovskii: Załóżmy, że jeden miał int DoSomething<T>() where T:Base {return T.GetNumber();}. Przydałoby się, gdyby DoSomething<Base>()mógł zwrócić pięć, a DoSomething<Child2>()dwa. Taka umiejętność byłaby przydatna nie tylko w przykładach zabawek, ale także w przypadku czegoś takiego class Car {public static virtual Car Build(PurchaseOrder PO);}, w którym każda klasa wywodząca się z niego Carmusiałaby zdefiniować metodę, która mogłaby zbudować instancję na podstawie zamówienia.
supercat
4
Istnieje dokładnie ten sam „problem” z dziedziczeniem niestatycznym.
Ark-kun
18

Inny respondent (McDowell) powiedział, że polimorfizm działa tylko dla instancji obiektów. To powinno być kwalifikowane; istnieją języki, które traktują klasy jako instancje typu „Class” lub „Metaclass”. Te języki obsługują polimorfizm zarówno dla metod instancji, jak i klas (statycznych).

C #, podobnie jak wcześniej Java i C ++, nie jest takim językiem; staticparametr jest używany do oznaczenia wyraźnie, że metoda jest statycznie związana zamiast dynamiczny / wirtualny.

Chris Hanson
źródło
9

Oto sytuacja, w której zdecydowanie istnieje potrzeba dziedziczenia pól i metod statycznych:

abstract class Animal
{
  protected static string[] legs;

  static Animal() {
    legs=new string[0];
  }

  public static void printLegs()
  {
    foreach (string leg in legs) {
      print(leg);
    }
  }
}


class Human: Animal
{
  static Human() {
    legs=new string[] {"left leg", "right leg"};
  }
}


class Dog: Animal
{
  static Dog() {
    legs=new string[] {"left foreleg", "right foreleg", "left hindleg", "right hindleg"};
  }
}


public static void main() {
  Dog.printLegs();
  Human.printLegs();
}


//what is the output?
//does each subclass get its own copy of the array "legs"?
użytkownik275801
źródło
4
Nie, istnieje tylko jedna instancja „nogi” tablicy. Dane wyjściowe nie są deterministyczne, ponieważ nie wiadomo, w jakiej kolejności będą wywoływane konstruktory statyczne (w rzeczywistości nie ma gwarancji, że w ogóle zostanie wywołany konstruktor statyczny klasy podstawowej). „Potrzeba” jest dość absolutnym terminem, w którym „pożądanie” jest prawdopodobnie bardziej dokładne.
Sam
legspowinna być statyczną abstrakcyjną własnością.
Little Endian,
8

Aby dodać do poprzednich wyjaśnień, statyczne wywołania metod są powiązane z określoną metodą w czasie kompilacji , co raczej wyklucza zachowanie polimorficzne.

Rytmis
źródło
C # jest wpisany statycznie; Wywołania metod polimorficznych są również powiązane w czasie kompilacji, tak jak to rozumiem - to znaczy, że CLR nie jest w stanie rozstrzygnąć, którą metodę wywołać w czasie wykonywania.
Adam Tolley,
Jak myślisz, jak dokładnie polimorfizm działa na CLR? Twoje wyjaśnienie po prostu wykluczyło wirtualną wysyłkę metod.
Rytmis
To nie jest tak przydatny komentarz, jak mógłby być. Zaprosiłem (z „tak, jak rozumiem”) użyteczny dyskurs, myślę, że mógłbyś podać nieco więcej treści - widząc, jak ludzie przychodzą tutaj, szukając odpowiedzi, a nie obelg. Chociaż wydaje się, że mogę być winny tego samego - tak naprawdę miałem na myśli powyższy komentarz jako pytanie: czy C # nie ocenia tych rzeczy w czasie kompilacji?
Adam Tolley,
Przepraszam, nie miałam na myśli obelgi (choć przyznaję, że odpowiedziałam nieco leniwie ;-). Chodzi mi o to, czy masz te klasy: class Base {public virtual void Method (); } klasa Derived: Base {public override void Method (); } i napisz w ten sposób: Base instance = new Derived (); instance.Method (); informacja o czasie kompilacji w witrynie wywoływania jest taka, że ​​mamy instancję Base, podczas gdy faktyczna instancja to Derived. Dlatego kompilator nie może rozwiązać dokładnej metody wywołania. Zamiast tego emituje instrukcję IL „callvirt”, która mówi środowisku wykonawczemu, aby wysłać.
Rytmis
1
Dzięki stary, to pouczające! Chyba wystarczająco długo odkładałem nurkowanie w IL, życzę powodzenia.
Adam Tolley
5

W rzeczywistości zastępujemy metody statyczne (w delphi), jest to trochę brzydkie, ale działa dobrze dla naszych potrzeb.

Używamy go, aby klasy mogły mieć listę dostępnych obiektów bez instancji klasy, na przykład mamy metodę, która wygląda następująco:

class function AvailableObjects: string; override;
begin
  Result := 'Object1, Object2';
end; 

Jest to brzydkie, ale konieczne, w ten sposób możemy utworzyć instancję tylko tego, co jest potrzebne, zamiast tworzenia instancji wszystkich klas tylko w celu wyszukiwania dostępnych obiektów.

To był prosty przykład, ale sama aplikacja jest aplikacją klient-serwer, która ma wszystkie klasy dostępne na jednym serwerze i wielu różnych klientów, którzy mogą nie potrzebować wszystkiego, co serwer ma i nigdy nie będzie potrzebować instancji obiektu.

Jest to o wiele łatwiejsze w utrzymaniu niż posiadanie jednej aplikacji serwera dla każdego klienta.

Mam nadzieję, że przykład był jasny.

Fabio Gomes
źródło
0

Metody abstrakcyjne są domyślnie wirtualne. Metody abstrakcyjne wymagają wystąpienia, ale metody statyczne nie mają wystąpienia. Tak więc możesz mieć metodę statyczną w klasie abstrakcyjnej, to po prostu nie może być statyczna abstrakcja (lub abstrakcyjna statyczność).

AMing
źródło
1
-1 wirtualne metody nie wymagają wystąpienia, z wyjątkiem projektu. I tak naprawdę nie zajmujesz się tym pytaniem, tylko je odsuwasz.
FallenAvatar,