Dlaczego nie mogę dziedziczyć klas statycznych?


224

Mam kilka klas, które tak naprawdę nie potrzebują żadnego stanu. Z organizacyjnego punktu widzenia chciałbym umieścić je w hierarchii.

Ale wydaje się, że nie mogę zadeklarować dziedziczenia dla klas statycznych.

Coś w tym stylu:

public static class Base
{
}

public static class Inherited : Base
{
}

nie będzie działać.

Dlaczego projektanci języka zamknęli tę możliwość?


Odpowiedzi:


174

Cytowanie stąd :

To jest właściwie z założenia. Wydaje się, że nie ma dobrego powodu do dziedziczenia klasy statycznej. Ma publiczne statyczne elementy, do których zawsze można uzyskać dostęp poprzez samą nazwę klasy. Jedyne powody, dla których widziałem dziedziczenie rzeczy statycznych, to złe, takie jak zapisanie kilku znaków na klawiaturze.

Mogą istnieć powody, aby rozważyć mechanizmy bezpośredniego wprowadzania elementów statycznych w zakres (i faktycznie rozważymy to po cyklu produktu Orcas), ale dziedziczenie klas statycznych nie jest dobrym rozwiązaniem: jest to niewłaściwy mechanizm, który działa i działa tylko dla elementów statycznych, które znajdują się w klasie statycznej.

(Mads Torgersen, PM C # Language)

Inne opinie z kanału9

Dziedziczenie w .NET działa tylko na podstawie instancji. Metody statyczne są zdefiniowane na poziomie typu, a nie na poziomie instancji. Dlatego zastępowanie nie działa z metodami statycznymi / właściwościami / zdarzeniami ...

Metody statyczne są przechowywane tylko raz w pamięci. Nie ma dla nich wirtualnej tabeli itp.

Jeśli wywołujesz metodę instancji w .NET, zawsze nadajesz jej bieżącą instancję. Jest to ukryte przez środowisko uruchomieniowe .NET, ale tak się dzieje. Każda metoda instancji ma jako pierwszy argument wskaźnik (odwołanie) do obiektu, na którym metoda jest uruchamiana. Nie dzieje się tak w przypadku metod statycznych (ponieważ są one zdefiniowane na poziomie typu). W jaki sposób kompilator powinien zdecydować o wyborze metody do wywołania?

(littleguru)

I jako cenny pomysł, Littleguru ma częściowe „obejście” tego problemu: wzór Singletona .


92
Naprawdę brak wyobraźni po stronie Torgersena. Miałem całkiem dobry powód, aby to zrobić :)
Thorarin

10
Co powiesz na to? Masz dll, który nie jest open source / nie masz dostępu do jego źródła, powiedzmy NUnit. Chcesz rozszerzyć klasę Assert o metodę taką jak Throws <> (Action a) (tak, to też tam jest, ale w pewnym momencie tak nie było). Możesz dodać do swojego projektu Assert: NUnit.Framework.Assert klasy, a następnie dodaj metodę Rzuty. Problem rozwiązany. Łatwiej jest też użyć kodu ... zamiast wymyślać inną nazwę klasy i pamiętać tę nazwę, po prostu wpisz Assert. i zobacz dostępne metody.
user420667,

4
@KonradMorawski Nie można pisać metod rozszerzenia dla klas statycznych. stackoverflow.com/questions/249222/...
Martin Braun

2
@modiX Oczywiście. Źle mnie tu zrozumiałeś. Chodziło mi o to, że funkcjonalność wymagana w scenariuszu opisanym przez user420667 może być zapewniona ( w przyszłości ) w tak niewielkim stopniu, jak dopuszczenie metod rozszerzenia dla klas statycznych. Nie jest wymagane dziedziczenie statyczne. Dlatego jego scenariusz nie wydał mi się bardzo dobrym uzasadnieniem dla wprowadzenia statycznego dziedziczenia. Jeśli C # miałby zostać zmodyfikowany w tym aspekcie, po co decydować się na duży przeprojektowanie, gdy mniejszy byłby równie dobry w praktyce.
Konrad Morawski

5
@ Uczeń To niesprawiedliwe. W .Net wszystko dziedziczy objecti każdy powinien to wiedzieć. Tak więc klasy statyczne zawsze dziedziczą object, bez względu na to, czy zostanie to określone jawnie, czy nie.
RenniePet,

71

Głównym powodem, dla którego nie można odziedziczyć klasy statycznej, jest to, że są one abstrakcyjne i zapieczętowane (zapobiega to również tworzeniu ich instancji).

Więc to:

static class Foo { }

kompiluje się do tej IL:

.class private abstract auto ansi sealed beforefieldinit Foo
  extends [mscorlib]System.Object
 {
 }

17
Mówisz, że statyczny został zaimplementowany jako abstrakcyjny + zapieczętowany. Chce wiedzieć, dlaczego tak się stało. Dlaczego jest zapieczętowany?
Lucas

22
To nie odpowiada na pytanie; to po prostu przypomina problem, o który pytał.
Igby Largeman

28
Cóż, OP powinien był zapytać „Dlaczego są zapieczętowane klasy statyczne”, a nie „Dlaczego nie mogę dziedziczyć po klasach statycznych?”, Odpowiedź brzmi oczywiście „ponieważ są zapieczętowane” . Ta odpowiedź dostaje mój głos.
Alex Budovski

6
dzięki za udostępnienie, że klasy statyczne są automatycznie kompilowane jako klasy zamknięte - zastanawiałem się, jak / kiedy to się stało!
Mark

23
@AlexBudovski: Ta odpowiedź jest poprawna, ale równie bezużyteczna jak mówienie zgubionej osobie „jesteś przede mną”, gdy pytają cię „gdzie jestem”.
DeepSpace101

24

Pomyśl o tym w ten sposób: uzyskujesz dostęp do elementów statycznych za pomocą nazwy typu, w następujący sposób:

MyStaticType.MyStaticMember();

Gdybyś odziedziczył po tej klasie, musiałbyś uzyskać do niej dostęp poprzez nazwę nowego typu:

MyNewType.MyStaticMember();

Zatem nowy element nie ma żadnych relacji z oryginałem, gdy jest używany w kodzie. Nie byłoby sposobu, aby wykorzystać jakąkolwiek relację dziedziczenia dla takich rzeczy jak polimorfizm.

Być może myślisz, że chcesz po prostu rozszerzyć niektóre przedmioty z oryginalnej klasy. W takim przypadku nic nie stoi na przeszkodzie, abyś po prostu użył członka oryginału w zupełnie nowym typie.

Być może chcesz dodać metody do istniejącego typu statycznego. Możesz to zrobić już za pomocą metod rozszerzenia.

Być może chcesz móc przekazać statyczną Typefunkcję do funkcji w czasie wykonywania i wywołać metodę tego typu, nie wiedząc dokładnie, co robi ta metoda. W takim przypadku możesz użyć interfejsu.

Ostatecznie tak naprawdę nie zyskujesz na odziedziczeniu klas statycznych.


5
Nie można dodawać metod do istniejącego typu statycznego za pomocą metody rozszerzenia. Proszę zobaczyć mój komentarz do zaakceptowanej odpowiedzi na przykład pożądanego zastosowania. (Zasadniczo chciałbyś po prostu zmienić nazwę, którą nazwałeś MyNewType na MyStaticType, więc MyStaticType: OldProject.MyStaticType)
user420667 11.11.11

2
Można zdefiniować relacje dziedziczenia z typami statycznymi i wirtualnymi elementami statycznymi w taki sposób, aby a SomeClass<T> where T:Foomógł uzyskać dostęp do elementów Foo, a gdyby Tbyła klasa, która przesłoniłaby niektóre wirtualne elementy statyczne Foo, klasa ogólna użyłaby tych przesłonięć. Prawdopodobnie byłoby nawet możliwe zdefiniowanie konwencji, dzięki której języki mogłyby to robić w sposób zgodny z obecnym CLR (np. Klasa z takimi elementami powinna zdefiniować chronioną klasę niestatyczną, która zawiera takie elementy instancji, wraz z polem statycznym z instancją tego typu).
supercat

Znalazłem przypadek, w którym uważam, że dziedziczenie klasy statycznej jest słuszne, chociaż oczywiście i tak można uzyskać do nich dostęp. Mam klasę statyczną do wysyłania nieprzetworzonych strumieni danych do drukarki (do rozmowy z przemysłowymi drukarkami etykiet). Ma kilka deklaracji API systemu Windows. Teraz piszę inną klasę, aby zadzierać z drukarkami, które potrzebują tych samych wywołań API. Połączyć? Niedobrze. Deklarować je dwukrotnie? Brzydki. Dziedziczyć? Dobrze, ale nie wolno.
Loren Pechtel,

3

To, co chcesz osiągnąć za pomocą hierarchii klas, można osiągnąć jedynie poprzez przestrzeń nazw. Tak więc języki, które obsługują nazwy (np. C #), nie będą mogły wykorzystywać hierarchii klas klas statycznych. Ponieważ nie można utworzyć żadnej z klas, wystarczy hierarchiczna organizacja definicji klas, którą można uzyskać za pomocą przestrzeni nazw


Mam ogólny moduł ładujący, który odbiera klasę jako typ. Ta klasa ma 5 metod pomocniczych i 2 metody zwracające ciąg znaków (nazwa i opis). Mogłem źle wybrać (tak mi się wydaje), ale jedynym rozwiązaniem, jakie znalazłem, było utworzenie instancji klasy w ogólnym module ładującym, aby uzyskać dostęp do metod ... meh.
ANeves

Chyba alternatywą byłby gigantyczny słownik. Ponadto, kiedy mówisz „co chcesz osiągnąć”, powinieneś zamiast tego powiedzieć „do czego dążysz” lub coś podobnego.
ANeves

3

Zamiast tego możesz użyć kompozycji ... pozwoli ci to uzyskać dostęp do obiektów klasy od typu statycznego. Ale nadal nie mogę zaimplementować interfejsów lub klas abstrakcyjnych


2

Chociaż można uzyskać dostęp do „odziedziczonych” elementów statycznych za pośrednictwem dziedziczonej nazwy klas, elementy statyczne nie są tak naprawdę dziedziczone. Po części dlatego nie mogą być wirtualne ani abstrakcyjne i nie można ich zastąpić. W twoim przykładzie, jeśli zadeklarujesz Base.Method (), kompilator i tak odwzoruje wywołanie na Inherited.Method () z powrotem na Base.Method (). Równie dobrze możesz wywołać funkcję Base.Method (). Możesz napisać mały test i zobaczyć wynik za pomocą Odbłyśnika.

Więc ... jeśli nie możesz dziedziczyć elementów statycznych, a jeśli klasy statyczne mogą zawierać tylko elementy statyczne, to co pożyteczne byłoby dziedziczenie klasy statycznej?


1

Hmmm ... czy byłoby znacznie inaczej, gdybyś miał klasy niestatyczne wypełnione metodami statycznymi ..?


2
To mi zostało. Robię to w ten sposób. Nie podoba mi się to.
Użytkownik

Zrobiłem to również w ten sposób, ale z jakiegoś powodu nie jest estetyczne, kiedy wiesz, że nigdy nie utworzysz obiektu. Zawsze dręczę się takimi szczegółami, mimo że nie wiąże się to z żadnymi kosztami materialnymi.
gdbj

W klasie niestatycznej wypełnionej metodami statycznymi nie można umieszczać metod rozszerzania :)
Jakub Szułakiewicz

1

Obejściem, które można zrobić, jest nieużywanie klas statycznych, ale ukrywanie konstruktora, więc elementy statyczne klas są jedyną dostępną poza klasą. Rezultatem jest dziedziczna „statyczna” klasa zasadniczo:

public class TestClass<T>
{
    protected TestClass()
    { }

    public static T Add(T x, T y)
    {
        return (dynamic)x + (dynamic)y;
    }
}

public class TestClass : TestClass<double>
{
    // Inherited classes will also need to have protected constructors to prevent people from creating instances of them.
    protected TestClass()
    { }
}

TestClass.Add(3.0, 4.0)
TestClass<int>.Add(3, 4)

// Creating a class instance is not allowed because the constructors are inaccessible.
// new TestClass();
// new TestClass<int>();

Niestety z powodu ograniczeń językowych „w fazie projektowania” nie możemy zrobić:

public static class TestClass<T>
{
    public static T Add(T x, T y)
    {
        return (dynamic)x + (dynamic)y;
    }
}

public static class TestClass : TestClass<double>
{
}

1

Możesz zrobić coś, co będzie wyglądać jak dziedziczenie statyczne.

Oto sztuczka:

public abstract class StaticBase<TSuccessor>
    where TSuccessor : StaticBase<TSuccessor>, new()
{
    protected static readonly TSuccessor Instance = new TSuccessor();
}

Następnie możesz to zrobić:

public class Base : StaticBase<Base>
{
    public Base()
    {
    }

    public void MethodA()
    {
    }
}

public class Inherited : Base
{
    private Inherited()
    {
    }

    public new static void MethodA()
    {
        Instance.MethodA();
    }
}

InheritedKlasa sama w sobie nie jest statyczna, ale nie pozwalają, aby go utworzyć. W rzeczywistości odziedziczył konstruktora statycznego, który buduje Base, a wszystkie właściwości i metody są Basedostępne jako statyczne. Teraz pozostało już tylko zrobić statyczne opakowania dla każdej metody i właściwości, które należy udostępnić w kontekście statycznym.

Są wady, takie jak potrzeba ręcznego tworzenia statycznych metod owijania i newsłów kluczowych. Ale to podejście pomaga wspierać coś, co jest naprawdę podobne do dziedziczenia statycznego.

PS Użyliśmy tego do tworzenia skompilowanych zapytań, które faktycznie można zastąpić ConcurrentDictionary, ale statyczne pole tylko do odczytu z jego bezpieczeństwem wątków było wystarczająco dobre.


1

Moja odpowiedź: zły wybór projektu. ;-)

To interesująca debata poświęcona wpływowi składni. Moim zdaniem rdzeniem tego argumentu jest to, że decyzja projektowa doprowadziła do zapieczętowanych klas statycznych. Skupiasz się na przejrzystości nazw klas statycznych pojawiających się na najwyższym poziomie zamiast ukrywania („mylenia”) za imionami dzieci? Można sobie wyobrazić implementację języka, która mogłaby uzyskać bezpośredni dostęp do bazy lub dziecka, co jest mylące.

Pseudo przykład, zakładający statyczne dziedziczenie, został w jakiś sposób zdefiniowany.

public static class MyStaticBase
{
    SomeType AttributeBase;
}

public static class MyStaticChild : MyStaticBase
{
    SomeType AttributeChild;
}

doprowadziłoby do:

 // ...
 DoSomethingTo(MyStaticBase.AttributeBase);
// ...

co może (mogłoby?) wpłynąć na to samo miejsce do przechowywania

// ...
DoSomethingTo(MyStaticChild.AttributeBase);
// ...

Bardzo mylące!

Ale poczekaj! Jak kompilator poradziłby sobie z MyStaticBase i MyStaticChild posiadającymi taki sam podpis zdefiniowany w obu? Jeśli dziecko zastąpi, niż mój powyższy przykład, NIE zmieniłoby tego samego magazynu, może? Prowadzi to do jeszcze większego zamieszania.

Uważam, że istnieje silne uzasadnienie przestrzeni informacyjnej dla ograniczonego dziedziczenia statycznego. Więcej o limitach wkrótce. Ten pseudokod pokazuje wartość:

public static class MyStaticBase<T>
{
   public static T Payload;
   public static void Load(StorageSpecs);
   public static void Save(StorageSpecs);
   public static SomeType AttributeBase
   public static SomeType MethodBase(){/*...*/};
}

Następnie otrzymujesz:

public static class MyStaticChild : MyStaticBase<MyChildPlayloadType>
{
   public static SomeType AttributeChild;
   public static SomeType SomeChildMethod(){/*...*/};
   // No need to create the PlayLoad, Load(), and Save().
   // You, 'should' be prevented from creating them, more on this in a sec...
} 

Sposób użycia wygląda następująco:

// ...
MyStaticChild.Load(FileNamePath);
MyStaticChild.Save(FileNamePath);
doSomeThing(MyStaticChild.Payload.Attribute);
doSomething(MyStaticChild.AttributeBase);
doSomeThing(MyStaticChild.AttributeChild);
// ...

Osoba tworząca statyczne dziecko nie musi myśleć o procesie serializacji, o ile rozumie wszelkie ograniczenia, które mogą zostać nałożone na silnik serializacji platformy lub środowiska.

Statyka (singletony i inne formy „globałów”) często pojawiają się wokół pamięci konfiguracji. Dziedziczenie statyczne pozwoliłoby na czystą reprezentację tego rodzaju alokacji odpowiedzialności w składni w celu dopasowania do hierarchii konfiguracji. Chociaż, jak pokazałem, istnieje duże prawdopodobieństwo ogromnej dwuznaczności, jeśli zostaną zaimplementowane podstawowe koncepcje dziedziczenia statycznego.

Uważam, że właściwym wyborem projektowym byłoby umożliwienie dziedziczenia statycznego z określonymi ograniczeniami:

  1. Żadnego zastąpienia. Dziecko nie może zastąpić podstawowych atrybutów, pól ani metod ... Przeciążenie powinno być w porządku, o ile istnieje różnica w podpisie pozwalająca kompilatorowi na uporządkowanie dziecka i bazy.
  2. Zezwalaj tylko na ogólne podstawy statyczne, nie możesz dziedziczyć po nieogólnych podstawach statycznych.

Nadal możesz zmienić ten sam sklep za pomocą ogólnego odniesienia MyStaticBase<ChildPayload>.SomeBaseField. Byłbyś jednak zniechęcony, ponieważ typ ogólny musiałby zostać określony. Natomiast odniesienie dziecko byłoby czystsze: MyStaticChild.SomeBaseField.

Nie jestem pisarzem kompilatorów, więc nie jestem pewien, czy coś mi brakuje na temat trudności we wdrażaniu tych ograniczeń w kompilatorze. To powiedziawszy, jestem głęboko przekonany, że istnieje przestrzeń informacyjna, która wymaga ograniczonego dziedziczenia statycznego, a podstawową odpowiedzią jest to, że nie możesz tego zrobić z powodu złego (lub zbyt prostego) wyboru projektu.


0

Klasy statyczne i elementy klasy służą do tworzenia danych i funkcji, do których można uzyskać dostęp bez tworzenia instancji klasy. Członków klasy statycznej można używać do oddzielania danych i zachowania niezależnego od tożsamości obiektu: dane i funkcje nie zmieniają się niezależnie od tego, co stanie się z obiektem. Klasy statyczne mogą być używane, gdy w klasie nie ma danych ani zachowań, które zależą od tożsamości obiektu.

Klasę można zadeklarować jako statyczną, co oznacza, że ​​zawiera ona tylko elementy statyczne. Nie można użyć nowego słowa kluczowego do utworzenia instancji klasy statycznej. Klasy statyczne są ładowane automatycznie przez środowisko uruchomieniowe języka wspólnego CLR .NET Framework po załadowaniu programu lub przestrzeni nazw zawierającej klasę.

Użyj klasy statycznej, aby zawierać metody, które nie są powiązane z konkretnym obiektem. Na przykład częstym wymogiem jest utworzenie zestawu metod, które nie działają na danych instancji i nie są powiązane z konkretnym obiektem w kodzie. Możesz użyć klasy statycznej do przechowywania tych metod.

Oto główne cechy klasy statycznej:

  1. Zawierają tylko elementy statyczne.

  2. Nie można ich utworzyć.

  3. Są zapieczętowane.

  4. Nie mogą zawierać Konstruktorów instancji (Podręcznik programowania w języku C #).

Utworzenie klasy statycznej jest zatem zasadniczo tym samym, co utworzenie klasy zawierającej tylko elementy statyczne i prywatnego konstruktora. Prywatny konstruktor zapobiega tworzeniu instancji klasy.

Zaletą używania klasy statycznej jest to, że kompilator może sprawdzić, aby upewnić się, że żadne elementy instancji nie zostaną przypadkowo dodane. Kompilator zagwarantuje, że nie można utworzyć instancji tej klasy.

Klasy statyczne są zapieczętowane i dlatego nie można ich dziedziczyć. Nie mogą dziedziczyć po żadnej klasie oprócz Object. Klasy statyczne nie mogą zawierać konstruktora instancji; mogą jednak mieć statycznego konstruktora. Aby uzyskać więcej informacji, zobacz Konstruktory statyczne (Podręcznik programowania w języku C #).


-1

Kiedy tworzymy klasę statyczną, która zawiera tylko elementy statyczne i konstruktora prywatnego. Jedynym powodem jest to, że konstruktor statyczny uniemożliwia utworzenie instancji klasy, w związku z czym nie możemy odziedziczyć klasy statycznej. Jedyny sposób, aby uzyskać dostęp do elementu klasa statyczna, używając samej nazwy klasy. Próba dziedziczenia klasy statycznej nie jest dobrym pomysłem.

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.