Dlaczego C # nie pozwala statycznym metodom na implementację interfejsu?


447

Dlaczego C # został zaprojektowany w ten sposób?

Jak rozumiem, interfejs opisuje tylko zachowanie i służy opisaniu zobowiązania umownego dla klas wdrażających interfejs, że określone zachowanie jest realizowane.

Jeśli klasy chcą zaimplementować to zachowanie we wspólnej metodzie, dlaczego nie powinny?

Oto przykład tego, co mam na myśli:

// These items will be displayed in a list on the screen.
public interface IListItem {
  string ScreenName();
  ...
}

public class Animal: IListItem {
    // All animals will be called "Animal".
    public static string ScreenName() {
        return "Animal";
    }
....
}

public class Person: IListItem {

    private string name;

    // All persons will be called by their individual names.
    public string ScreenName() {
        return name;
    }

    ....

 }

6
Cóż, Java 8 ma to ( stackoverflow.com/questions/23148471/... ).
liang

1
Zobacz, jak połączyć zachowanie statyczne z dziedziczeniem lub implementacją interfejsu: stackoverflow.com/a/13567309/880990
Olivier Jacot-Descombes

1
IListItem.ScreenName() => ScreenName()(przy użyciu składni C # 7) jawnie zaimplementuje metodę interfejsu, wywołując metodę statyczną. Kiedy dodaje się do tego dziedziczenie, robi się brzydko (trzeba ponownie zaimplementować interfejs)
Jeroen Mostert

2
Po prostu daję znać wszystkim, że czekanie już się skończyło! C # 8.0 ma statyczne metody interfejsu: dotnetfiddle.net/Lrzy6y (chociaż działają one nieco inaczej niż OP chciał, aby działały - nie trzeba ich implementować)
Jamie Twells

Odpowiedzi:


221

Zakładając, że pytasz, dlaczego nie możesz tego zrobić:

public interface IFoo {
    void Bar();
}

public class Foo: IFoo {
    public static void Bar() {}
}

To nie ma dla mnie sensu semantycznie. Powinny istnieć metody określone w interfejsie w celu określenia umowy dotyczącej interakcji z obiektem. Metody statyczne nie pozwalają na interakcję z obiektem - jeśli znajdziesz się w pozycji, w której twoja implementacja może stać się statyczna, być może będziesz musiał zadać sobie pytanie, czy ta metoda naprawdę należy do interfejsu.


Aby zaimplementować twój przykład, nadałbym zwierzęciu właściwość const, która nadal umożliwiałaby dostęp do niego ze statycznego kontekstu i zwróciłaby tę wartość w implementacji.

public class Animal: IListItem {
    /* Can be tough to come up with a different, yet meaningful name!
     * A different casing convention, like Java has, would help here.
     */
    public const string AnimalScreenName = "Animal";
    public string ScreenName(){ return AnimalScreenName; }
}

W przypadku bardziej skomplikowanej sytuacji zawsze możesz zadeklarować inną metodę statyczną i delegować ją. Próbując wymyślić przykład, nie mogłem wymyślić żadnego powodu, dla którego zrobiłbyś coś nietrywialnego zarówno w kontekście statycznym, jak i instancji, więc oszczędzę ci obiektu blob FooBar i wezmę to za wskazówkę, że może nie być dobrym pomysłem.


6
Idealny przykład! Ale nie jestem pewien, czy rozumiem twoje rozumowanie. Z pewnością kompilator mógł zostać zaprojektowany tak, aby uwzględniał również członków statuc? Czy instancje nie mają tabeli adresów do implementacji tych metod? Czy w tej tabeli nie można uwzględnić metod statycznych?
Kramii

12
Jest przypadek, w którym może to być przydatne. Na przykład chcę, aby wszystkie implementatory zaimplementowały metodę GetInstance, która pobiera argument XElement. Nie mogę zdefiniować tego jako metody statycznej w interfejsie, ani żądać podpisu konstruktora z interfejsu.
oleks

25
Wielu ludzi zastanowiło się po obu stronach: od „To nie ma sensu” do „To błąd, chciałbym, żebyś mógł”. (Wydaje mi się, że istnieją uzasadnione przypadki użycia, i tak właśnie tu się skończyłem.) W celu obejścia tego problemu metoda instancji może po prostu przekazać metodę statyczną.
harpo

5
Możesz także po prostu zaimplementować element jako metodę rozszerzenia interfejsu podstawowego, jak w: public static object MethodName(this IBaseClass base)w klasie statycznej. Minusem jest jednak to, że w przeciwieństwie do dziedziczenia interfejsu - nie wymusza to / nie pozwala poszczególnym spadkobiercom na dobre zastąpienie metodologii.
Troy Alford,

8
Miałoby to sens z lekami generycznymi. Na przykład void Something <T> () gdzie T: ISomeInterface {new T (). DoSomething1 (); T.DoSomething2 (); }
Francisco Ryan Tolmasky I

173

Moim (uproszczonym) technicznym powodem jest to, że metod statycznych nie ma w tabeli vt , a strona połączenia jest wybierana w czasie kompilacji. Jest to ten sam powód, dla którego nie można zastąpić ani wirtualnego elementu statycznego. Aby uzyskać więcej informacji, potrzebujesz pakietu CS grad lub kompilatora - z których ja też nie jestem.

Ze względów politycznych zacytuję Erica Lipperta (który jest winkiem kompilatora i posiada licencjat z matematyki, informatyki i matematyki stosowanej z University of Waterloo (źródło: LinkedIn ):

... podstawową zasadą projektowania metod statycznych, zasadą, która nadaje im ich nazwę ... [jest] ... zawsze można dokładnie określić, w czasie kompilacji, jaka metoda zostanie wywołana. Oznacza to, że metodę można rozwiązać wyłącznie poprzez statyczną analizę kodu.

Zauważ, że Lippert nie pozostawia miejsca na tak zwaną metodę typu:

To znaczy metoda powiązana z typem (jak statyczny), która nie przyjmuje niedopuszczalnego argumentu „ten” (w przeciwieństwie do instancji lub wirtualnego), ale metoda, w której wywoływana metoda zależałaby od skonstruowanego typu T ( w przeciwieństwie do statycznego, który musi być możliwy do ustalenia w czasie kompilacji).

ale trzeba się jeszcze przekonać o jego przydatności.


5
Doskonale, to odpowiedź, którą chciałem napisać - po prostu nie znałem szczegółów implementacji.
Chris Marasti-Georg,

5
Świetna odpowiedź. I chcę tej „metody typu”! Przydałby się w wielu przypadkach (pomyśl o metadanych dla typu / klasy).
Philip Daubmeier,

23
To jest poprawna odpowiedź. Przekazujesz interfejs komuś, który musi wiedzieć, jak wywołać metodę. Interfejs to tylko wirtualna tabela metod . Twoja klasa statyczna tego nie ma. Dzwoniący nie wiedziałby, jak wywołać metodę. (Przed czytałem tę odpowiedź Myślałam C # był po prostu zbyt pedantyczny. Teraz zdaję sobie sprawę, że to ograniczenie techniczne, nałożone przez co interfejs jest ). Inne osoby będą z tobą rozmawiać o tym, jak źle wygląda. To nie jest zły projekt - jest to ograniczenie techniczne.
Ian Boyd,

2
+1 za faktyczne udzielenie odpowiedzi na pytanie i nie bycie pedantycznym i zasmarkanym. Jak powiedział Ian Boyd: „To nie jest zły projekt - to techniczne ograniczenie”.
Joshua Pech

3
Całkowicie możliwe jest wygenerowanie obiektu dla klasy statycznej z powiązanym vtable. Zobacz, jak Scala obsługuje objects i jak mogą implementować interfejsy.
Sebastian Graf

97

Większość odpowiedzi tutaj wydaje się pomijać cały punkt. Polimorfizm można stosować nie tylko między instancjami, ale także między typami. Jest to często potrzebne, gdy używamy leków generycznych.

Załóżmy, że mamy parametr typu w metodzie ogólnej i musimy wykonać z nim pewną operację. Nie chcemy tworzyć instancji, ponieważ nie jesteśmy świadomi konstruktorów.

Na przykład:

Repository GetRepository<T>()
{
  //need to call T.IsQueryable, but can't!!!
  //need to call T.RowCount
  //need to call T.DoSomeStaticMath(int param)
}

...
var r = GetRepository<Customer>()

Niestety mogę wymyślić tylko „brzydkie” alternatywy:

  • Użyj refleksji Ugly i przebija ideę interfejsów i polimorfizmu.

  • Utwórz całkowicie oddzielną klasę fabryczną

    Może to znacznie zwiększyć złożoność kodu. Na przykład, jeśli próbujemy modelować obiekty domeny, każdy obiekt potrzebuje innej klasy repozytorium.

  • Utwórz instancję, a następnie wywołaj żądaną metodę interfejsu

    Może to być trudne do wdrożenia, nawet jeśli kontrolujemy źródło klas, używane jako parametry ogólne. Powodem jest to, że na przykład możemy potrzebować, aby instancje były tylko w dobrze znanym stanie „podłączony do bazy danych”.

Przykład:

public class Customer 
{
  //create new customer
  public Customer(Transaction t) { ... }

  //open existing customer
  public Customer(Transaction t, int id) { ... }

  void SomeOtherMethod() 
  { 
    //do work...
  }
}

aby użyć rozwiązania do rozwiązywania problemu statycznego interfejsu, musimy wykonać następujące czynności:

public class Customer: IDoSomeStaticMath
{
  //create new customer
  public Customer(Transaction t) { ... }

  //open existing customer
  public Customer(Transaction t, int id) { ... }

  //dummy instance
  public Customer() { IsDummy = true; }

  int DoSomeStaticMath(int a) { }

  void SomeOtherMethod() 
  { 
    if(!IsDummy) 
    {
      //do work...
    }
  }
}

Jest to oczywiście brzydkie, a także niepotrzebne komplikuje kod dla wszystkich innych metod. Oczywiście nie jest to również eleganckie rozwiązanie!


35
+1 za „Większość odpowiedzi tutaj wydaje się pomijać cały punkt.”; to niewiarygodne, jak się wydaje, że prawie wszystkie odpowiedzi unikają sedna pytania, aby zagłębić się w najczęściej

5
@Chris Oto konkretny przykład, który zmusił mnie do ponownego dotknięcia tego ograniczenia. Chcę dodać interfejs IResettable do klas, aby wskazać, że buforują one pewne dane w zmiennych statycznych, które mogą być resetowane przez administratora strony (np. Lista kategorii zamówień, zestaw stawek vat, lista kategorii pobranych z zewnętrznego API) aby zredukować trafienia DB i zewnętrznego API, oczywiście użyłoby to resetu metody statycznej. To pozwala mi po prostu zautomatyzować wykrywanie, które klasy można zresetować. Nadal mogę to zrobić, ale metoda nie jest wymuszana ani automatycznie dodawana do IDE i polega na nadziei.
mattmanser

6
@Chris Nie zgadzam się, ogromna przesada. To zawsze oznaka wady językowej, gdy więcej architektury jest „najlepszym” rozwiązaniem. Pamiętasz wszystkie wzorce, o których nikt już nie mówi, odkąd C # ma generyczne i anonimowe metody?
mattmanser

3
Nie możesz użyć „where T: IQueryable, T: IDoSomeStaticMath” lub podobnego?
Roger Willcocks,

2
@ ChrisMarasti-Georg: Nieco brudna, ale interesująca metoda polega na tym, że ta mała konstrukcja: public abstract class DBObject<T> where T : DBObject<T>, new()a następnie sprawi, że wszystkie klasy DB odziedziczą jako DBObject<T>. Następnie możesz ustawić funkcję pobierania według klucza jako funkcję statyczną z typem zwracanym T w abstrakcyjnej nadklasie, zmusić tę funkcję do utworzenia nowego obiektu T, a następnie wywołać chroniony String GetRetrieveByKeyQuery()(zdefiniowany jako abstrakcyjny w nadklasie) na tym obiekcie, aby uzyskać rzeczywiste zapytanie do wykonania. Chociaż może to być odrywane od tematu
Nyerguds

20

Wiem, że to stare pytanie, ale interesujące. Przykład nie jest najlepszy. Myślę, że byłoby znacznie jaśniej, gdybyś pokazał przypadek użycia:

string DoSomething <T> () gdzie T: ISomeFunction
{
  if (T.someFunction ())
    ...
}

Zwykłe posiadanie statycznych metod implementujących interfejs nie osiągnęłoby tego, co chcesz; potrzebne byłyby elementy statyczne jako część interfejsu. Z pewnością mogę sobie wyobrazić wiele przypadków użycia, szczególnie jeśli chodzi o możliwość tworzenia rzeczy. Dwa podejścia, które mogę zaoferować, które mogą być pomocne:

  1. Utwórz statyczną klasę ogólną, której parametrem typu będzie typ, który będziesz przekazywał do DoSomething powyżej. Każda odmiana tej klasy będzie miała co najmniej jednego statycznego członka trzymającego rzeczy związane z tym typem. Informacje te mogą być dostarczane albo przez wywołanie każdej klasy zainteresowań procedury „rejestrowania informacji”, albo przez użycie Reflection w celu uzyskania informacji, gdy uruchomiony jest statyczny konstruktor odmiany klasy. Wierzę, że to drugie podejście jest używane przez rzeczy takie jak Comparer <T> .Default ().
  2. Dla każdej interesującej klasy T zdefiniuj klasę lub strukturę, która implementuje IGetWhthingClassInfo <T> i spełnia „nowe” ograniczenie. Klasa faktycznie nie będzie zawierać żadnych pól, ale będzie miała właściwość statyczną, która zwraca pole statyczne z informacjami o typie. Przekaż typ tej klasy lub struktury do ogólnej procedury, o której mowa, która będzie mogła utworzyć instancję i użyć jej do uzyskania informacji o drugiej klasie. Jeśli używasz do tego celu klasy, prawdopodobnie powinieneś zdefiniować statyczną klasę ogólną, jak wskazano powyżej, aby uniknąć konieczności tworzenia nowej instancji deskryptora-obiektu za każdym razem. Jeśli używasz struktury, koszt tworzenia instancji powinien wynosić zero, ale każdy inny typ struktury wymagałby innego rozszerzenia procedury DoSomething.

Żadne z tych podejść nie jest tak naprawdę atrakcyjne. Z drugiej strony spodziewałbym się, że gdyby mechanizmy istniały w CLR w celu zapewnienia tego rodzaju funkcjonalności w sposób czysty, .net pozwoliłby na określenie sparametryzowanych „nowych” ograniczeń (ponieważ poznanie, czy klasa ma konstruktora z określoną sygnaturą, wydaje się trudności w porównywaniu z wiedzą, czy ma metodę statyczną z określonym podpisem).


16

Krótkowzroczność, tak sądzę.

Pierwotnie zaprojektowane interfejsy miały być używane tylko z instancjami klasy

IMyInterface val = GetObjectImplementingIMyInterface();
val.SomeThingDefinedinInterface();

Dopiero wraz z wprowadzeniem interfejsów, ponieważ ograniczenia generyczne rzeczywiście dodały statyczną metodę do interfejsu, miały praktyczne zastosowanie.

(odpowiadając na komentarz :) Wierzę, że zmiana go teraz wymagałaby zmiany CLR, co doprowadziłoby do niezgodności z istniejącymi zestawami.


To właśnie w kontekście ogólnych po raz pierwszy napotkałem problem, ale zastanawiam się, czy włączenie metod statycznych do interfejsów mogłoby być przydatne również w innych kontekstach? Czy istnieje powód, dla którego rzeczy nie można zmienić?
Kramii

ja również spotykam się z tym podczas implementowania klasy ogólnej, która wymaga typu parametru, aby utworzyć się z pewnymi parametrami. Ponieważ new () nie może przyjąć żadnego. Czy już wymyśliłeś, jak to zrobić, Kramii?
Tom

1
@Kramii: Umowy dotyczące statycznych interfejsów API. Nie chcę wystąpienia obiektu, tylko gwarancję określonego podpisu, np. IMatrixMultiplier lub ICustomSerializer. Funcs / Akcje / Delegaci jako członkowie klasy robią to samo, ale IMO czasami wydaje się to przesadą i może być mylące dla niedoświadczonych prób rozszerzenia API.
David Cuccia,

14

Interfejsy określają zachowanie obiektu.

Metody statyczne nie określają zachowania obiektu, ale zachowanie, które w jakiś sposób wpływa na obiekt.


71
Przepraszam .. Nie jestem pewien, czy to prawda! Interfejs nie określa zachowania. Interfejs definiuje zestaw nazwanych operacji. Dwie klasy mogłyby zaimplementować metodę interfejsu, aby zachowywać się w zupełnie inny sposób. Interfejs w ogóle nie określa zachowania. Klasa, która ją implementuje.
Scott Langham,

1
Mam nadzieję, że nie uważasz, że jestem wybredna ... ale myślę, że to ważne wyróżnienie dla każdego, kto uczy się rozumieć OO.
Scott Langham

4
Interfejs ma określać umowę, która obejmuje zachowanie i prezentację. Dlatego zmiana zachowania wywołania interfejsu jest nie-nie, ponieważ oba powinny zostać naprawione. Jeśli masz interfejs, w którym wywołanie działa inaczej (np. IList.Add zrobił usunięcie), nie byłoby dobrze.
Jeff Yates

14
Cóż, tak, zostałbyś wypaczony w głowie, aby zdefiniować metodę, która zachowuje się niezgodnie z jego nazwą. Jeśli miałeś IAlertService.GetAssistance (), jego zachowanie może polegać na zapaleniu światła, uruchomieniu alarmu lub szturchnięciu kijem w oko.
Scott Langham,

1
Implementacja byłaby również w stanie zapisać do pliku dziennika. To zachowanie nie jest określone w interfejsie. Ale może masz rację. Należy naprawdę przestrzegać zachowania polegającego na „uzyskaniu pomocy”.
Scott Langham,

14

W zakresie, w jakim interfejsy stanowią „kontrakty”, wydaje się, że klasy statyczne mogą implementować interfejsy.

Powyższe argumenty wydają się pomijać ten punkt dotyczący umów.


2
Całkowicie zgadzam się z tą prostą, ale skuteczną odpowiedzią. Interesujące w „interfejsie statycznym” jest to, że stanowiłoby ono umowę. Może nie należy tego nazywać „statycznym interfejsem”, ale wciąż brakuje nam konstrukcji. Na przykład sprawdź oficjalny dokument .NET na temat interfejsu ICustomMarshaler. Wymaga od klasy, która ją implementuje, „dodania metody statycznej o nazwie GetInstance, która akceptuje ciąg jako parametr i ma zwracany typ ICustomMarshaler”. To naprawdę wygląda jak definicja „statycznego interfejsu” w prostym języku angielskim, podczas gdy wolałbym to w C # ...
Simon Mourier

@ SimonMourier Ta dokumentacja mogła być napisana jaśniej, ale źle ją interpretujesz. To nie interfejs ICustomMarshaler wymaga statycznej metody GetInstance. Wymaga tego atrybut kodu [MarshalAs]. Używają wzorca fabrycznego, aby umożliwić atrybutowi uzyskanie instancji dołączonego marszałka. Niestety, zupełnie zapomnieli o dołączeniu dokumentacji wymaganej przez GetInstance na stronie dokumentacji MarshalAs (pokazuje tylko przykłady wykorzystujące wbudowane implementacje marshalingu).
Scott Gartner,

@ScottGartner - Nie rozumiem. msdn.microsoft.com/en-us/library/… wyraźnie stwierdza: „Oprócz implementacji interfejsu ICustomMarshaler, niestandardowi marszałkowie muszą zaimplementować metodę statyczną o nazwie GetInstance, która akceptuje ciąg znaków jako parametr i ma zwracany typ ICustomMarshaler. To metoda statyczna jest wywoływana przez warstwę COM środowiska uruchomieniowego wspólnego języka, aby utworzyć instancję niestandardowego marshalera. ". Jest to zdecydowanie statyczna definicja kontraktu.
Simon Mourier,

9

Ponieważ celem interfejsu jest umożliwienie polimorfizmu, możliwość przekazania instancji dowolnej liczby zdefiniowanych klas, które wszystkie zostały zdefiniowane, w celu implementacji zdefiniowanego interfejsu ... gwarantując, że w twoim wywołaniu polimorficznym kod będzie w stanie znaleźć metoda, którą wołasz. nie ma sensu zezwalać na statyczną metodę implementacji interfejsu,

Jak byś to nazwał?


public interface MyInterface { void MyMethod(); }
public class MyClass: MyInterface
{
    public static void MyMethod() { //Do Something; }
}

 // inside of some other class ...  
 // How would you call the method on the interface ???
    MyClass.MyMethod();  // this calls the method normally 
                         // not through the interface...

    // This next fails you can't cast a classname to a different type... 
    // Only instances can be Cast to a different type...
    MyInterface myItf = MyClass as MyInterface;  

1
Inne języki (na przykład Java) pozwalają na wywoływanie metod statycznych z instancji obiektów, chociaż pojawi się ostrzeżenie, że należy je wywoływać z kontekstu statycznego.
Chris Marasti-Georg,

Zezwolenie na wywołanie metody statycznej z instancji jest również dozwolone w .Net. To inna sprawa. Jeśli metoda statyczna zaimplementowała interfejs, możesz nazwać go BEZ instancji. TO nie ma sensu. Pamiętaj, że nie możesz umieścić implementacji w interfejsie, musi to być klasa. Więc jeśli zdefiniowano pięć różnych klas do implementacji tego interfejsu, a każda z nich miała inną implementację tej metody statycznej, z czego skorzystałby kompilator?
Charles Bretana,

1
@CharlesBretana w przypadku generics, ten dla typu przekazanego. Widzę dużą użyteczność w posiadaniu interfejsów dla metod statycznych (lub jeśli chcesz, nazwijmy je „interfejsami statycznymi” i pozwól klasie zdefiniować oba interfejsy statyczne i interfejsy instancji). Więc jeśli mam Whatever<T>() where T:IMyStaticInterface, mógłbym zadzwonić Whatever<MyClass>()i mieć T.MyStaticMethod()wewnątrz Whatever<T>()implementacji bez potrzeby wystąpienia. Metoda wywołania zostanie określona w czasie wykonywania. Możesz to zrobić poprzez refleksję, ale nie ma „umowy”, którą trzeba by wymusić.
Jcl

4

Jeśli chodzi o metody statyczne stosowane w kontekstach innych niż ogólne, zgadzam się, że nie ma sensu dopuszczać ich w interfejsach, ponieważ nie byłoby możliwe ich wywołanie, gdybyś miał odniesienie do interfejsu. Istnieje jednak fundamentalna dziura w projektowaniu języka utworzona przy użyciu interfejsów NIE w kontekście polimorficznym, ale w ogólnym. W tym przypadku interfejs wcale nie jest interfejsem, ale raczej ograniczeniem. Ponieważ C # nie ma pojęcia ograniczenia poza interfejsem, brakuje znacznej funkcjonalności. Przykładem:

T SumElements<T>(T initVal, T[] values)
{
    foreach (var v in values)
    {
        initVal += v;
    }
}

Tutaj nie ma polimorfizmu, rodzajowy używa rzeczywistego typu obiektu i wywołuje operator + =, ale to się nie udaje, ponieważ nie jest pewne, czy ten operator istnieje. Najprostszym rozwiązaniem jest określenie go w ograniczeniu; proste rozwiązanie jest niemożliwe, ponieważ operatory są statyczne, a metody statyczne nie mogą znajdować się w interfejsie, a (tutaj jest problem) ograniczenia są przedstawiane jako interfejsy.

To, czego potrzebuje C #, to prawdziwy typ ograniczenia, wszystkie interfejsy byłyby również ograniczeniami, ale nie wszystkie ograniczenia byłyby interfejsami, to możesz to zrobić:

constraint CHasPlusEquals
{
    static CHasPlusEquals operator + (CHasPlusEquals a, CHasPlusEquals b);
}

T SumElements<T>(T initVal, T[] values) where T : CHasPlusEquals
{
    foreach (var v in values)
    {
        initVal += v;
    }
}

Wiele mówi się już o tworzeniu IArithmetic dla wszystkich typów liczbowych do implementacji, ale istnieje obawa o wydajność, ponieważ ograniczenie nie jest konstrukcją polimorficzną, wprowadzenie ograniczenia CArithmetic rozwiązałoby ten problem.


Operatory w języku C # są statyczne, więc nadal nie ma to sensu.
John Saunders,

W tym sensie ograniczenie weryfikuje, że TYPE (nie instancja) ma operator + (a przez to operator + =) i pozwala na wygenerowanie szablonu, a następnie, gdy nastąpi faktyczne zastąpienie szablonu, używany obiekt jest gwarantowany typu, który ma ten operator (lub inną metodę statyczną). Możesz więc powiedzieć: SumElements <int> (0, 5); który utworzyłby to: SumElements (int initVal, int [] wartości) {foreach (var w wartościach) {initVal + = v; }}, co oczywiście ma sens
Jeremy Sorensen,

1
Pamiętaj, że to nie jest szablon. To ogólne, które nie są takie same jak szablony C ++.
John Saunders,

@JohnSaunders: Generics nie są szablonami i nie wiem, jak można je rozsądnie zmusić do współpracy z operatorami powiązanymi statycznie, ale w kontekstach ogólnych istnieje wiele przypadków, w których warto określić, że a Tpowinien mieć statyczny członek (np. fabryka produkująca Tinstancje). Myślę, że nawet bez zmian w środowisku wykonawczym byłoby możliwe zdefiniowanie konwencji i metod pomocniczych, które pozwoliłyby językom na wdrożenie czegoś takiego jak cukier syntaktyczny w efektywny i interoperacyjny sposób. Jeśli dla każdego interfejsu z wirtualnymi metodami statycznymi istniała klasa pomocnicza ...
supercat 14.12.2013

1
@JohnSaunders: Problem nie polega na tym, że metoda implementuje interfejs, ale na tym, że kompilator nie może wybrać metody wirtualnej do wywołania bez instancji obiektu, na której można by dokonać wyboru. Rozwiązaniem jest wysłanie wywołania „interfejsu statycznego” nie przy użyciu wirtualnej wysyłki (co nie będzie działać), ale zamiast tego przy użyciu wywołania do ogólnej klasy statycznej. Ogólne wysyłanie oparte na typach nie wymaga posiadania instancji, a jedynie posiadania typu.
supercat

3

Ponieważ interfejsy mają strukturę dziedziczenia, a metody statyczne nie dziedziczą dobrze.


3

Wydaje się, że chcesz, aby metoda statyczna była wywoływana zarówno przez typ, jak i dowolną instancję tego typu. Doprowadziłoby to przynajmniej do dwuznaczności, która nie jest pożądaną cechą.

Odbywałyby się niekończące się debaty na temat tego, czy ma to znaczenie, co jest najlepszą praktyką i czy występują problemy z wydajnością w ten czy inny sposób. Po prostu nie obsługując tego C # oszczędza nam martwienia się o to.

Jest również prawdopodobne, że kompilator, który spełniłby to pragnienie, utraciłby pewne optymalizacje, które mogą wynikać z bardziej ścisłego oddzielenia metod instancji od metod statycznych.


Co ciekawe, całkiem możliwe jest wywołanie metody statycznej przez jedno wystąpienie typu reklama w VB.Net (nawet jeśli IDE daje ostrzeżenie w tym drugim przypadku). To nie wydaje się być problemem. Być może masz rację co do optymalizacji.
Kramii

3

Można myśleć o metodach statycznych i metodach niestatycznych klasy jako o różnych interfejsach. Po wywołaniu metody statyczne są tłumaczone na obiekt klasy statycznej singleton, a metody niestatyczne na instancję klasy, z którą mamy do czynienia. Tak więc, jeśli użyjesz statycznych i niestatycznych metod w interfejsie, efektywnie zadeklarujesz dwa interfejsy, kiedy naprawdę chcemy, aby interfejsy były używane do uzyskania dostępu do jednej spójnej rzeczy.


Jest to interesujący POV i prawdopodobnie ten, o którym myśleli projektanci C #. Będę myśleć o elementach statycznych w inny sposób niż teraz.
Kramii

3

Aby podać przykład, w którym brakuje mi albo statycznej implementacji metod interfejsu, albo tego, co Mark Brackett wprowadził jako „tak zwaną metodę typu”:

Podczas odczytu z pamięci bazy danych mamy ogólną klasę DataTable, która obsługuje odczytywanie z tabeli o dowolnej strukturze. Wszystkie informacje specyficzne dla tabeli są umieszczane w jednej klasie na tabelę, która zawiera również dane dla jednego wiersza z bazy danych i która musi implementować interfejs IDataRow. W IDataRow zawarty jest opis struktury tabeli do odczytu z bazy danych. DataTable musi zapytać o strukturę danych z IDataRow przed odczytem z DB. Obecnie wygląda to tak:

interface IDataRow {
  string GetDataSTructre();  // How to read data from the DB
  void Read(IDBDataRow);     // How to populate this datarow from DB data
}

public class DataTable<T> : List<T> where T : IDataRow {

  public string GetDataStructure()
    // Desired: Static or Type method:
    // return (T.GetDataStructure());
    // Required: Instantiate a new class:
    return (new T().GetDataStructure());
  }

}

GetDataStructure jest wymagany tylko raz dla każdej tabeli do odczytu, narzut związany z tworzeniem jednej kolejnej instancji jest minimalny. Jednak w tym przypadku byłoby miło.


1

FYI: Możesz uzyskać podobne zachowanie do tego, co chcesz, tworząc metody rozszerzenia interfejsu. Metodą rozszerzenia byłoby wspólne, nieprzekraczalne zachowanie statyczne. Niestety, ta metoda statyczna nie byłaby częścią umowy.


1

Interfejsy to abstrakcyjne zestawy zdefiniowanych dostępnych funkcji.

To, czy metoda w tym interfejsie zachowuje się jako statyczna, czy nie, jest szczegółem implementacji, który powinien być ukryty za interfejsem . Błędem byłoby zdefiniowanie metody interfejsu jako statycznej, ponieważ niepotrzebnie zmuszałbyś metodę do implementacji w określony sposób.

Gdyby metody zostały zdefiniowane jako statyczne, klasa implementująca interfejs nie byłaby tak enkapsulowana, jak mogłaby być. Enkapsulacja jest dobrą rzeczą, do której należy dążyć w projektowaniu obiektowym (nie będę się zastanawiać, dlaczego, możesz przeczytać to tutaj: http://en.wikipedia.org/wiki/Object-oriented ). Z tego powodu metody statyczne nie są dozwolone w interfejsach.


1
Tak, statyczne w deklaracji interfejsu byłoby głupie. Klasa nie powinna być zmuszana do implementacji interfejsu w określony sposób. Jednak czy C # ogranicza klasę do implementacji interfejsu przy użyciu metod niestatycznych. Czy to ma sens? Dlaczego?
Kramii

Nie możesz użyć słowa kluczowego „statyczny”. Nie ma jednak żadnych ograniczeń, ponieważ nie potrzebujesz słowa kluczowego static, aby napisać metodę, która zachowuje się statycznie. Po prostu napisz metodę, która nie ma dostępu do żadnego z elementów obiektu, a następnie będzie się zachowywać jak metoda statyczna. Istnieje więc ograniczenie.
Scott Langham,

Prawda Scott, ale nie pozwala komuś na dostęp do tej metody w sposób statyczny, w innej części kodu. Nie to, że mogę wymyślić powód, dla którego chcesz, ale wydaje się, że o to pyta OP.
Chris Marasti-Georg,

Cóż, jeśli naprawdę odczuwasz potrzebę, możesz napisać go jako statyczną metodę dostępu w innej części kodu i po prostu napisz metodę niestatystyczną, aby wywołać metodę statyczną. Mogę się mylić, ale wątpię, żeby to był cel pytającego.
Scott Langham,

1

Klasy statyczne powinny być w stanie to zrobić, aby mogły być używane ogólnie. Zamiast tego musiałem wdrożyć Singleton, aby osiągnąć pożądane rezultaty.

Miałem kilka klas statycznych warstw biznesowych, w których zaimplementowano metody CRUD, takie jak „Utwórz”, „Przeczytaj”, „Aktualizuj”, „Usuń” dla każdego typu jednostki, np. „Użytkownik”, „Zespół” itp. Następnie utworzyłem bazę kontrola, która miała właściwość abstrakcyjną dla klasy warstwy biznesowej, która implementowała metody CRUD. Umożliwiło mi to zautomatyzowanie operacji „Utwórz”, „Przeczytaj”, „Aktualizuj”, „Usuń” z klasy podstawowej. Musiałem użyć Singletona z powodu ograniczeń statycznych.


1

Wydaje się, że większość ludzi zapomina, że ​​w klasach OOP są również obiekty, a więc mają wiadomości, które z jakiegoś powodu c # nazywają „metodą statyczną”. Fakt, że istnieją różnice między obiektami instancji a obiektami klasy, pokazuje tylko wady lub wady języka. Optymista o c # chociaż ...


2
Problem polega na tym, że to pytanie nie dotyczy „w OOP”. Chodzi o „in C #”. W języku C # nie ma „wiadomości”.
John Saunders,

1

OK, oto przykład potrzeby użycia „metody typu”. Tworzę jeden z zestawu klas opartych na źródłowym pliku XML. Więc mam

  static public bool IsHandled(XElement xml)

funkcja wywoływana z kolei w każdej klasie.

Funkcja powinna być statyczna, ponieważ w przeciwnym razie tracimy czas na tworzenie nieodpowiednich obiektów. Jak zauważa @Ian Boyde, można tego dokonać w klasie fabrycznej, ale to tylko zwiększa złożoność.

Byłoby miło dodać go do interfejsu, aby zmusić implementatory klasy do jego wdrożenia. Nie spowodowałoby to znacznego obciążenia - to tylko kontrola czasu kompilacji / łącza i nie wpływa na vtable.

Byłaby to jednak dość niewielka poprawa. Ponieważ metoda jest statyczna, ja jako osoba wywołująca muszę ją jawnie wywołać, więc otrzymam natychmiastowy błąd kompilacji, jeśli nie zostanie zaimplementowany. Zezwolenie na określenie go w interfejsie oznaczałoby, że ten błąd pojawia się nieznacznie wcześniej w cyklu programowania, ale jest to trywialne w porównaniu z innymi problemami z uszkodzonym interfejsem.

Jest to więc niewielka potencjalna cecha, która prawdopodobnie jest najlepiej pominięta.


1

Fakt, że klasa statyczna jest zaimplementowana w języku C # przez Microsoft, tworząc specjalną instancję klasy z elementami statycznymi, jest po prostu osobliwością w jaki sposób osiąga się funkcjonalność statyczną. To nie jest teoretyczny punkt.

Interfejs MUSI być deskryptorem interfejsu klasy - lub jego interakcji, i powinien obejmować interakcje, które są statyczne. Ogólna definicja interfejsu (z Meriam-Webster): miejsce lub obszar, w którym różne rzeczy spotykają się i komunikują ze sobą lub wpływają na siebie. Kiedy całkowicie pomijasz elementy statyczne klasy lub klasy statyczne, ignorujemy duże sekcje interakcji tych złych chłopców.

Oto bardzo wyraźny przykład, w którym użyteczne byłoby używanie interfejsów z klasami statycznymi:

public interface ICrudModel<T, Tk>
{
    Boolean Create(T obj);
    T Retrieve(Tk key);
    Boolean Update(T obj);
    Boolean Delete(T obj);
}

Obecnie piszę klasy statyczne zawierające te metody bez żadnego sprawdzania, aby upewnić się, że niczego nie zapomniałem. To jak złe czasy programowania przed OOP.


To jest starożytne pytanie; obecnie bardzo staramy się unikać pytań opartych na opiniach, ponieważ trudno jest na nie odpowiedzieć autorytatywnie. Jedynym dobrym sposobem na udzielenie odpowiedzi byłoby opisanie powodów, dla których stwardnienie rozsiane miało lub wyraźnie powinno było mieć takie postępowanie.
Nathan Tuggy

1

C # i CLR powinny obsługiwać metody statyczne w interfejsach, podobnie jak Java. Modyfikator statyczny jest częścią definicji kontraktu i ma znaczenie, w szczególności, że zachowanie i wartość zwracana nie różnią się w zależności od instancji, chociaż może się różnić w zależności od połączenia.

To powiedziawszy, polecam, jeśli chcesz użyć metody statycznej w interfejsie i nie możesz, zamiast tego użyj adnotacji. Otrzymasz funkcjonalność, której szukasz.


0

Myślę, że krótka odpowiedź brzmi „ponieważ ma zerową przydatność”. Aby wywołać metodę interfejsu, potrzebujesz instancji typu. Z metod instancji można wywoływać dowolne metody statyczne.


0

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.

Ponieważ nie jest dostępny, zacząłem używać metod niestatycznych w sposób pokazany poniżej. Niezupełnie idealne, ale nie widzę żadnego podejścia, które miałoby większy sens, przynajmniej nie dla mnie.

public interface IZeroWrapper<TNumber> {
  TNumber Zero {get;}
}

public class DoubleWrapper: IZeroWrapper<double> {
  public double Zero { get { return 0; } }
}

0

Zgodnie z koncepcją obiektową Interfejs realizowany przez klasy i umowy na dostęp do tych zaimplementowanych funkcji (lub metod) za pomocą obiektu.

Jeśli więc chcesz uzyskać dostęp do metod umowy o interfejs, musisz utworzyć obiekt. Zawsze jest to niedozwolone w przypadku metod statycznych. Klasy statyczne, metoda i zmienne nigdy nie wymagają obiektów i ładowania do pamięci bez tworzenia obiektu tego obszaru (lub klasy), lub można powiedzieć, że nie wymagają tworzenia obiektu.


0

Koncepcyjnie nie ma powodu, dla którego interfejs nie mógłby zdefiniować kontraktu zawierającego metody statyczne.

W przypadku bieżącej implementacji języka C # ograniczenie wynika z możliwości dziedziczenia klasy podstawowej i interfejsów. Jeśli „klasa SomeBaseClass” zaimplementuje „interfejs ISomeInterface” i „klasa SomeDerivedClass: SomeBaseClass, ISomeInterface” również zaimplementuje interfejs, metoda statyczna w celu zaimplementowania metody interfejsu zakończy się niepowodzeniem, ponieważ metoda statyczna nie może mieć takiej samej sygnatury jak metoda instancji (co być obecnym w klasie podstawowej w celu wdrożenia interfejsu).

Klasa statyczna jest funkcjonalnie identyczna z singletonem i służy temu samemu celowi, co singleton z czystszą składnią. Ponieważ singleton może implementować interfejs, implementacje interfejsu według statyki są poprawne koncepcyjnie.

Sprowadza się to po prostu do ograniczenia na przykład konfliktu nazw C # i metod statycznych o tej samej nazwie w ramach dziedziczenia. Nie ma powodu, dla którego język C # nie mógłby zostać „zaktualizowany” do obsługi statycznych kontraktów metod (interfejsów).


-1

Kiedy klasa implementuje interfejs, tworzy instancję dla elementów interfejsu. Chociaż typ statyczny nie ma instancji, nie ma sensu mieć podpisów statycznych w interfejsie.

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.