Warum erlaubt C # statischen Methoden nicht, eine Schnittstelle zu implementieren?

447

Warum wurde C # so entworfen?

So wie ich es verstehe, beschreibt eine Schnittstelle nur das Verhalten und dient dem Zweck, eine vertragliche Verpflichtung für Klassen zu beschreiben, die die Schnittstelle implementieren, dass ein bestimmtes Verhalten implementiert wird.

Wenn Klassen dieses Verhalten in einer gemeinsamen Methode implementieren möchten, warum sollten sie es nicht tun?

Hier ist ein Beispiel für das, was ich vorhabe:

// 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;
    }

    ....

 }
Kramii
quelle
6
Nun, Java 8 hat es ( stackoverflow.com/questions/23148471/… ).
Liang
1
Sehen Sie, wie Sie ein statisches Verhalten mit Vererbung oder Schnittstellenimplementierung kombinieren können: stackoverflow.com/a/13567309/880990
Olivier Jacot-Descombes
1
IListItem.ScreenName() => ScreenName()(unter Verwendung der C # 7-Syntax) implementiert die Schnittstellenmethode explizit durch Aufrufen der statischen Methode. Die Dinge werden jedoch hässlich, wenn Sie die Vererbung hinzufügen (Sie müssen die Schnittstelle neu implementieren)
Jeroen Mostert
2
Lassen Sie einfach alle wissen, dass das Warten jetzt vorbei ist! C # 8.0 hat statische Schnittstellenmethoden: dotnetfiddle.net/Lrzy6y (obwohl sie ein wenig anders funktionieren, als OP es wollte - Sie müssen sie nicht implementieren)
Jamie Twells

Antworten:

221

Angenommen, Sie fragen, warum Sie dies nicht tun können:

public interface IFoo {
    void Bar();
}

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

Das macht für mich semantisch keinen Sinn. Auf einer Schnittstelle angegebene Methoden sollten vorhanden sein, um den Vertrag für die Interaktion mit einem Objekt anzugeben. Mit statischen Methoden können Sie nicht mit einem Objekt interagieren. Wenn Sie sich in der Position befinden, in der Ihre Implementierung statisch gemacht werden könnte, müssen Sie sich möglicherweise fragen, ob diese Methode wirklich zur Schnittstelle gehört.


Um Ihr Beispiel zu implementieren, würde ich Animal eine const-Eigenschaft geben, mit der weiterhin über einen statischen Kontext darauf zugegriffen werden kann, und diesen Wert in der Implementierung zurückgeben.

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

In einer komplizierteren Situation können Sie jederzeit eine andere statische Methode deklarieren und an diese delegieren. Bei dem Versuch, ein Beispiel zu finden, konnte ich mir keinen Grund vorstellen, warum Sie etwas nicht Triviales sowohl im statischen als auch im Instanzkontext tun würden. Deshalb erspare ich Ihnen einen FooBar-Blob und nehme ihn als Hinweis darauf, dass dies möglich ist keine gute Idee sein.

Chris Marasti-Georg
quelle
6
Ein perfektes Beispiel! Aber ich bin nicht sicher, ob ich Ihre Argumentation verstehe. Sicherlich hätte der Compiler auch die statuc-Mitglieder betrachten können? Haben die Instanzen keine Adresstabelle für die Implementierung dieser Methoden? Könnten die statischen Methoden nicht in diese Tabelle aufgenommen werden?
Kramii
12
Es gibt einen Fall, in dem dies nützlich sein könnte. Ich möchte beispielsweise, dass alle Implementierer eine GetInstance-Methode implementieren, die ein XElement-Argument akzeptiert. Ich kann das weder als statische Methode in der Schnittstelle definieren, noch eine Konstruktorsignatur von der Schnittstelle verlangen.
Oleks
25
Viele Leute haben sich auf beiden Seiten eingemischt: von "Das macht keinen Sinn" bis "Es ist ein Fehler, ich wünschte du könntest es." (Ich denke, es gibt gültige Anwendungsfälle dafür, und so bin ich hier gelandet.) Als Problemumgehung kann die Instanzmethode einfach an eine statische Methode delegieren.
Harpo
5
Sie können das Teil auch einfach als Erweiterungsmethode auf der Basisschnittstelle implementieren, wie in: public static object MethodName(this IBaseClass base)innerhalb einer statischen Klasse. Der Nachteil ist jedoch, dass im Gegensatz zu einer Schnittstellenvererbung die einzelnen Erben nicht gezwungen werden, die Methodik gut zu überschreiben.
Troy Alford
8
Bei Generika wäre das sehr sinnvoll. Zum Beispiel void Something <T> () wobei T: ISomeInterface {new T (). DoSomething1 (); T.DoSomething2 (); }
Francisco Ryan Tolmasky I
173

Mein (vereinfachter) technischer Grund ist, dass statische Methoden nicht in der vtable enthalten sind und die Aufrufsite zur Kompilierungszeit ausgewählt wird. Es ist der gleiche Grund, warum Sie keine Override- oder virtuellen statischen Mitglieder haben können. Für weitere Details benötigen Sie einen CS-Absolventen oder einen Compiler-Wonk - von denen ich keiner bin.

Aus politischen Gründen zitiere ich Eric Lippert (der ein Compiler Wonk ist und einen Bachelor of Mathematics, Informatik und Angewandte Mathematik von der University of Waterloo besitzt (Quelle: LinkedIn ):

... das Kernentwurfsprinzip statischer Methoden, das Prinzip, das ihnen ihren Namen gibt ... [ist] ... es kann immer genau bestimmt werden, welche Methode zur Kompilierungszeit aufgerufen wird. Das heißt, die Methode kann ausschließlich durch statische Analyse des Codes aufgelöst werden.

Beachten Sie, dass Lippert Platz für eine sogenannte Typmethode lässt:

Das heißt, eine Methode, die einem Typ zugeordnet ist (wie eine statische), die kein nicht nullbares "this" -Argument verwendet (im Gegensatz zu einer Instanz oder einer virtuellen), sondern eine, bei der die aufgerufene Methode vom konstruierten Typ von T abhängt ( im Gegensatz zu einer statischen, die zur Kompilierungszeit bestimmbar sein muss).

ist aber noch von seiner Nützlichkeit zu überzeugen.

Mark Brackett
quelle
5
Ausgezeichnet, das ist die Antwort, die ich schreiben wollte - ich kannte die Implementierungsdetails einfach nicht.
Chris Marasti-Georg
5
Gute Antwort. Und ich möchte diese 'Typmethode'! Wäre in so vielen Fällen nützlich (denken Sie an Metadaten für einen Typ / eine Klasse).
Philip Daubmeier
23
Dies ist die richtige Antwort. Wenn Sie eine Schnittstelle an jemanden übergeben, muss dieser wissen, wie eine Methode aufgerufen wird. Eine Schnittstelle ist nur eine virtuelle Methodentabelle . Ihre statische Klasse hat das nicht. Der Aufrufer würde nicht wissen, wie er eine Methode aufrufen soll. (Bevor ich diese Antwort las, dachte ich, C # sei nur pedantisch. Jetzt ist mir klar, dass es sich um eine technische Einschränkung handelt, die durch eine Schnittstelle auferlegt wird. ) Andere Leute werden mit Ihnen darüber sprechen, wie schlecht das Design ist. Es ist kein schlechtes Design - es ist eine technische Einschränkung.
Ian Boyd
2
+1 für die tatsächliche Beantwortung der Frage und nicht pedantisch und rotzig. Wie Ian Boyd sagte: "Es ist kein schlechtes Design - es ist eine technische Einschränkung."
Joshua Pech
3
Es ist durchaus möglich, ein Objekt für eine statische Klasse mit zugehöriger vtable zu generieren. Sehen Sie sich an, wie Scala mit objects umgeht und wie sie Schnittstellen implementieren dürfen.
Sebastian Graf
97

Die meisten Antworten hier scheinen den ganzen Punkt zu verfehlen. Polymorphismus kann nicht nur zwischen Instanzen, sondern auch zwischen Typen verwendet werden. Dies wird oft benötigt, wenn wir Generika verwenden.

Angenommen, wir haben einen Typparameter in der generischen Methode und müssen einige Operationen damit ausführen. Wir wollen nicht instanziieren, weil wir die Konstruktoren nicht kennen.

Zum Beispiel:

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

Leider kann ich mir nur "hässliche" Alternativen einfallen lassen:

  • Verwenden Sie Reflexion Hässlich und schlägt die Idee von Schnittstellen und Polymorphismus.

  • Erstellen Sie eine vollständig separate Factory-Klasse

    Dies kann die Komplexität des Codes erheblich erhöhen. Wenn wir beispielsweise versuchen, Domänenobjekte zu modellieren, benötigt jedes Objekt eine andere Repository-Klasse.

  • Instanziieren Sie und rufen Sie dann die gewünschte Schnittstellenmethode auf

    Dies kann schwierig zu implementieren sein, selbst wenn wir die Quelle für die Klassen steuern, die als generische Parameter verwendet werden. Der Grund dafür ist, dass sich die Instanzen möglicherweise nur im bekannten Status "Mit DB verbunden" befinden müssen.

Beispiel:

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

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

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

Um die Instanziierung zur Lösung des statischen Schnittstellenproblems zu verwenden, müssen wir Folgendes tun:

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...
    }
  }
}

Dies ist offensichtlich hässlich und auch unnötig kompliziert den Code für alle anderen Methoden. Natürlich auch keine elegante Lösung!

Ivan Arjentinski
quelle
35
+1 für "Die meisten Antworten hier scheinen den ganzen Punkt zu verfehlen."; Es ist unglaublich, wie es scheint, dass so ziemlich alle Antworten dem Kern der Frage ausweichen, um sich mit größtenteils unbrauchbaren Worten zu befassen ...
5
@ Chris Hier ist ein konkretes Beispiel, das mich dazu gebracht hat, diese Einschränkung erneut zu treffen. Ich möchte Klassen eine IResettable-Schnittstelle hinzufügen, um anzuzeigen, dass sie bestimmte Daten in statischen Variablen zwischenspeichern, die vom Site-Administrator zurückgesetzt werden können (z. B. eine Liste der Auftragskategorien, eine Reihe von Mehrwertsteuersätzen, eine Liste der Kategorien, die von einer externen API abgerufen wurden). Um die DB- und externen API-Treffer zu reduzieren, würde dies offensichtlich ein Zurücksetzen der statischen Methode verwenden. Dadurch kann ich nur die Erkennung automatisieren, welche Klassen zurückgesetzt werden können. Ich kann dies immer noch tun, aber die Methode wird in der IDE nicht erzwungen oder automatisch hinzugefügt und beruht auf Hoffnung.
Mattmanser
6
@ Chris Ich bin anderer Meinung, massiver Overkill. Es ist immer ein Zeichen für einen Sprachfehler, wenn mehr Architektur die "beste" Lösung ist. Erinnern Sie sich an all die Muster, über die niemand mehr spricht, seit C # Generika und anonyme Methoden hat?
Mattmanser
3
Können Sie nicht "where T: IQueryable, T: IDoSomeStaticMath" oder ähnliches verwenden?
Roger Willcocks
2
@ ChrisMarasti-Georg: Ein etwas schmutziger, aber interessanter Weg ist diese kleine Konstruktion : public abstract class DBObject<T> where T : DBObject<T>, new(), und dann alle DB-Klassen als erben lassen DBObject<T>. Dann können Sie das Abrufen per Schlüssel zu einer statischen Funktion mit dem Rückgabetyp T in der abstrakten Oberklasse machen, diese Funktion ein neues Objekt von T erstellen lassen und dann ein geschütztes String GetRetrieveByKeyQuery()(in der Oberklasse als abstrakt definiertes) Objekt für dieses Objekt aufrufen, um es abzurufen die tatsächlich auszuführende Abfrage. Obwohl dies ein bisschen vom Thema
abkommen
20

Ich weiß, dass es eine alte Frage ist, aber es ist interessant. Das Beispiel ist nicht das beste. Ich denke, es wäre viel klarer, wenn Sie einen Anwendungsfall zeigen würden:

Zeichenfolge DoSomething <T> () wobei T: ISomeFunction
{
  if (T.someFunction ())
    ...
}}

Nur statische Methoden eine Schnittstelle implementieren zu lassen , würde nicht das erreichen, was Sie wollen. Was benötigt würde, wäre, statische Elemente als Teil einer Schnittstelle zu haben. Ich kann mir sicherlich viele Anwendungsfälle dafür vorstellen, besonders wenn es darum geht, Dinge erstellen zu können. Zwei Ansätze, die ich anbieten könnte und die hilfreich sein könnten:

  1. Erstellen Sie eine statische generische Klasse, deren Typparameter der Typ ist, den Sie oben an DoSomething übergeben würden. Jede Variation dieser Klasse hat ein oder mehrere statische Mitglieder, die Dinge enthalten, die mit diesem Typ zusammenhängen. Diese Informationen können entweder bereitgestellt werden, indem jede interessierende Klasse eine "Registerinformations" -Routine aufruft, oder indem Reflection verwendet wird, um die Informationen abzurufen, wenn der statische Konstruktor der Klassenvariante ausgeführt wird. Ich glaube, der letztere Ansatz wird von Dingen wie Comparer <T> .Default () verwendet.
  2. Definieren Sie für jede interessierende Klasse T eine Klasse oder Struktur, die IGetWhateverClassInfo <T> implementiert und eine "neue" Einschränkung erfüllt. Die Klasse enthält eigentlich keine Felder, verfügt jedoch über eine statische Eigenschaft, die ein statisches Feld mit den Typinformationen zurückgibt. Übergeben Sie den Typ dieser Klasse oder Struktur an die betreffende generische Routine, die eine Instanz erstellen und damit Informationen über die andere Klasse abrufen kann. Wenn Sie zu diesem Zweck eine Klasse verwenden, sollten Sie wahrscheinlich eine statische generische Klasse wie oben angegeben definieren, um zu vermeiden, dass jedes Mal eine neue Deskriptorobjektinstanz erstellt werden muss. Wenn Sie eine Struktur verwenden, sollten die Instanziierungskosten gleich Null sein, aber jeder unterschiedliche Strukturtyp würde eine andere Erweiterung der DoSomething-Routine erfordern.

Keiner dieser Ansätze ist wirklich ansprechend. Andererseits würde ich erwarten, dass, wenn die Mechanismen in CLR vorhanden wären, um diese Art von Funktionalität sauber bereitzustellen, .net es erlauben würde, parametrisierte "neue" Einschränkungen anzugeben (da zu wissen scheint, ob eine Klasse einen Konstruktor mit einer bestimmten Signatur hat in Schwierigkeiten vergleichbar sein mit dem Wissen, ob es eine statische Methode mit einer bestimmten Signatur gibt).

Superkatze
quelle
16

Kurzsichtigkeit, würde ich vermuten.

Bei der ursprünglichen Entwicklung sollten Schnittstellen nur für Klasseninstanzen verwendet werden

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

Erst mit der Einführung von Schnittstellen als Einschränkungen für Generika hatte das Hinzufügen einer statischen Methode zu einer Schnittstelle eine praktische Verwendung.

(Antwort auf Kommentar :) Ich glaube, eine Änderung würde jetzt eine Änderung der CLR erfordern, was zu Inkompatibilitäten mit vorhandenen Baugruppen führen würde.

James Curran
quelle
Im Zusammenhang mit Generika bin ich zum ersten Mal auf das Problem gestoßen, aber ich frage mich, ob das Einfügen statischer Methoden in Schnittstellen auch in anderen Kontexten nützlich sein könnte. Gibt es einen Grund, warum Dinge nicht geändert werden konnten?
Kramii
Auch ich stoße darauf, wenn ich eine generische Klasse implementiere, für die der Parametertyp erforderlich ist, um sich mit einigen Parametern selbst zu erstellen. Da kann das neue () keine nehmen. Hast du schon herausgefunden, wie das geht, Kramii?
Tom
1
@Kramii: Verträge für statische APIs. Ich möchte keine Objektinstanz, nur eine Garantie für eine bestimmte Signatur, z. IMatrixMultiplier oder ICustomSerializer. Funktionen / Aktionen / Delegierte als Klassenmitglieder machen den Trick, aber IMO scheint dies manchmal übertrieben und kann für den unerfahrenen Versuch, die API zu erweitern, verwirrend sein.
David Cuccia
14

Schnittstellen geben das Verhalten eines Objekts an.

Statische Methoden geben kein Verhalten eines Objekts an, sondern ein Verhalten, das ein Objekt in irgendeiner Weise beeinflusst.

John Kraft
quelle
71
Entschuldigung .. Ich bin mir nicht sicher, ob das richtig ist! Eine Schnittstelle gibt kein Verhalten an. Eine Schnittstelle definiert eine Reihe benannter Operationen. Zwei Klassen könnten die Methode einer Schnittstelle implementieren, um sich völlig unterschiedlich zu verhalten. Eine Schnittstelle gibt also überhaupt kein Verhalten an. Die Klasse, die es implementiert, tut es.
Scott Langham
1
Ich hoffe, Sie glauben nicht, dass ich wählerisch bin. Aber ich denke, es ist ein wichtiger Unterschied für jeden, der OO lernt, zu verstehen.
Scott Langham
4
Eine Schnittstelle soll einen Vertrag angeben, der Verhalten und Präsentation enthält. Aus diesem Grund ist das Ändern des Verhaltens eines Schnittstellenaufrufs ein Nein-Nein, da beide behoben werden sollen. Wenn Sie eine Schnittstelle haben, in der der Anruf anders funktioniert (dh IList.Add hat eine Entfernung durchgeführt), wäre dies nicht richtig.
Jeff Yates
14
Ja, Sie würden sich im Kopf verziehen, um eine Methode zu definieren, die sich unpassend zu ihrem Namen verhält. Wenn Sie jedoch IAlertService.GetAssistance () hatten, könnte das Verhalten darin bestehen, ein Licht zu blinken, einen Alarm auszulösen oder mit einem Stock ins Auge zu stechen.
Scott Langham
1
Eine Implementierung könnte auch in eine Protokolldatei schreiben. Dieses Verhalten ist in der Schnittstelle nicht angegeben. Aber vielleicht hast du recht. Das Verhalten, um Hilfe zu erhalten, sollte wirklich respektiert werden.
Scott Langham
14

In dem Maße, in dem Schnittstellen "Verträge" darstellen, erscheint es für statische Klassen ziemlich vernünftig, Schnittstellen zu implementieren.

Die obigen Argumente scheinen alle diesen Punkt über Verträge zu verfehlen.

George
quelle
2
Ich stimme dieser einfachen, aber effektiven Antwort voll und ganz zu. Interessant an "statischer Schnittstelle" wäre, dass es sich um einen Vertrag handelt. Vielleicht sollte es nicht als "statische Schnittstelle" bezeichnet werden, aber wir vermissen immer noch ein Konstrukt. Überprüfen Sie beispielsweise das offizielle Dokument von .NET über die ICustomMarshaler-Schnittstelle. Die Klasse, die es implementiert, muss "eine statische Methode namens GetInstance hinzufügen, die einen String als Parameter akzeptiert und den Rückgabetyp ICustomMarshaler hat". Das sieht ernsthaft aus wie eine "statische Schnittstelle" Definition in einfachem Englisch, während ich es in C # bevorzugen würde ...
Simon Mourier
@SimonMourier Diese Dokumentation hätte klarer geschrieben werden können, aber Sie interpretieren sie falsch. Es ist nicht die ICustomMarshaler-Schnittstelle, die die statische Methode GetInstance erfordert. Dies ist das Code-Attribut [MarshalAs], das dies erfordert. Sie verwenden das Factory-Muster, damit das Attribut eine Instanz des angeschlossenen Marshallers abrufen kann. Leider haben sie völlig vergessen, die GetInstance-Anforderung auf der MarshalAs-Dokumentationsseite zu dokumentieren (es werden nur Beispiele mit integrierten Marshalling-Implementierungen angezeigt).
Scott Gartner
@ScottGartner - Verstehe deinen Standpunkt nicht. In msdn.microsoft.com/en-us/library/… heißt es eindeutig: "Zusätzlich zur Implementierung der ICustomMarshaler-Schnittstelle müssen benutzerdefinierte Marshaller eine statische Methode namens GetInstance implementieren, die einen String als Parameter akzeptiert und den Rückgabetyp ICustomMarshaler hat Die statische Methode wird von der COM-Interop-Schicht der Common Language Runtime aufgerufen, um eine Instanz des benutzerdefinierten Marshallers zu instanziieren. " Dies ist definitiv eine statische Vertragsdefinition.
Simon Mourier
9

Da der Zweck einer Schnittstelle darin besteht, Polymorphismus zuzulassen, kann eine Instanz einer beliebigen Anzahl definierter Klassen übergeben werden, die alle zur Implementierung der definierten Schnittstelle definiert wurden. Dies garantiert, dass der Code innerhalb Ihres polymorphen Aufrufs gefunden werden kann die Methode, die Sie aufrufen. Es macht keinen Sinn, eine statische Methode zur Implementierung der Schnittstelle zuzulassen.

Wie würdest du es nennen?


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;  
Charles Bretana
quelle
1
In anderen Sprachen (z. B. Java) können statische Methoden von Objektinstanzen aus aufgerufen werden. Sie erhalten jedoch eine Warnung, dass Sie sie aus einem statischen Kontext aufrufen sollten.
Chris Marasti-Georg
Das Aufrufen einer statischen Methode von einer Instanz aus ist auch in .Net zulässig. Das ist etwas anderes. Wenn eine statische Methode eine Schnittstelle implementiert, können Sie sie OHNE Instanz aufrufen. DAS macht keinen Sinn. Denken Sie daran, dass Sie die Implementierung nicht in eine Schnittstelle einfügen können, sondern in der Klasse. Wenn also fünf verschiedene Klassen definiert würden, um diese Schnittstelle zu implementieren, und jede eine andere Implementierung dieser statischen Methode hätte, welche würde der Compiler verwenden?
Charles Bretana
1
@CharlesBretana im Fall von Generika, dem für den übergebenen Typ. Ich sehe viel Nützlichkeit darin, Schnittstellen für statische Methoden zu haben (oder wenn Sie möchten, nennen wir sie "statische Schnittstellen" und erlauben einer Klasse, beide statischen Schnittstellen zu definieren und Instanzschnittstellen). Wenn ich also Whatever<T>() where T:IMyStaticInterfacehabe, könnte ich anrufen Whatever<MyClass>()und T.MyStaticMethod()in der Whatever<T>()Implementierung haben, ohne eine Instanz zu benötigen. Die aufzurufende Methode wird zur Laufzeit festgelegt. Sie können dies durch Reflexion tun, aber es gibt keinen "Vertrag", der erzwungen werden muss.
Jcl
4

In Bezug auf statische Methoden, die in nicht generischen Kontexten verwendet werden, stimme ich zu, dass es wenig sinnvoll ist, sie in Schnittstellen zuzulassen, da Sie sie nicht aufrufen könnten, wenn Sie ohnehin einen Verweis auf die Schnittstelle hätten. Es gibt jedoch eine grundlegende Lücke im Sprachdesign, die durch die Verwendung von Schnittstellen NICHT in einem polymorphen, sondern in einem generischen Kontext entsteht. In diesem Fall ist die Schnittstelle überhaupt keine Schnittstelle, sondern eine Einschränkung. Da C # kein Konzept für eine Einschränkung außerhalb einer Schnittstelle hat, fehlen wesentliche Funktionen. Ein typisches Beispiel:

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

Hier gibt es keinen Polymorphismus, das Generikum verwendet den tatsächlichen Typ des Objekts und ruft den Operator + = auf. Dies schlägt jedoch fehl, da nicht sicher gesagt werden kann, dass dieser Operator vorhanden ist. Die einfache Lösung besteht darin, sie in der Einschränkung anzugeben. Die einfache Lösung ist unmöglich, da Operatoren statisch sind und statische Methoden nicht in einer Schnittstelle enthalten sein können und (hier ist das Problem) Einschränkungen als Schnittstellen dargestellt werden.

Was C # benötigt, ist ein echter Einschränkungstyp. Alle Schnittstellen wären ebenfalls Einschränkungen, aber nicht alle Einschränkungen wären Schnittstellen. Dann könnten Sie Folgendes tun:

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

Es wurde bereits viel darüber gesprochen, eine IArithmetik für alle zu implementierenden numerischen Typen zu erstellen, aber es gibt Bedenken hinsichtlich der Effizienz, da eine Einschränkung kein polymorphes Konstrukt ist und eine CArithmetische Einschränkung dieses Problem lösen würde.

Jeremy Sorensen
quelle
Operatoren in C # sind statisch, daher macht dies immer noch keinen Sinn.
John Saunders
Das ist der Punkt, an dem die Einschränkung überprüft, ob der TYP (nicht die Instanz) den Operator + (und damit den Operator + =) hat und die Generierung der Vorlage ermöglicht. Wenn die tatsächliche Ersetzung der Vorlage erfolgt, ist das verwendete Objekt garantiert von einem Typ, der diesen Operator (oder eine andere statische Methode) hat. Sie können also sagen: SumElements <int> (0, 5); was dies instanziieren würde: SumElements (int initVal, int [] Werte) {foreach (var v in Werten) {initVal + = v; }}, was natürlich durchaus Sinn macht
Jeremy Sorensen
1
Denken Sie daran, es ist keine Vorlage. Es sind Generika, die nicht mit C ++ - Vorlagen identisch sind.
John Saunders
@JohnSaunders: Generika sind keine Vorlagen, und ich weiß nicht, wie sie vernünftigerweise für die Arbeit mit statisch gebundenen Operatoren verwendet werden können, aber in generischen Kontexten gibt es viele Fälle, in denen es nützlich sein kann, anzugeben, dass a Teine statische haben soll Mitglied (zB eine Fabrik, die TInstanzen produziert ). Selbst ohne Laufzeitänderungen denke ich, dass es möglich wäre, Konventionen und Hilfsmethoden zu definieren, mit denen Sprachen so etwas wie syntaktischen Zucker effizient und interoperabel implementieren können. Wenn es für jede Schnittstelle mit virtuellen statischen Methoden eine
Hilfsklasse gäbe
1
@JohnSaunders: Das Problem ist nicht, dass die Methode eine Schnittstelle implementiert, sondern dass der Compiler keine virtuelle Methode zum Aufrufen auswählen kann, ohne über eine Objektinstanz zu verfügen, auf der die Auswahl basieren kann. Eine Lösung besteht darin, den Aufruf der "statischen Schnittstelle" nicht über den virtuellen Versand (der nicht funktioniert), sondern über einen Aufruf einer generischen statischen Klasse zu versenden. Für den typbasierten generischen Versand ist keine Instanz erforderlich, sondern lediglich ein Typ.
Supercat
3

Da sich die Schnittstellen in der Vererbungsstruktur befinden und statische Methoden nicht gut erben.

Joel Coehoorn
quelle
3

Was Sie zu wollen scheinen, würde es ermöglichen, eine statische Methode sowohl über den Typ als auch über eine beliebige Instanz dieses Typs aufzurufen. Dies würde zumindest zu Mehrdeutigkeiten führen, die kein wünschenswertes Merkmal sind.

Es würde endlose Debatten darüber geben, ob es darauf ankommt, was die beste Vorgehensweise ist und ob es Leistungsprobleme gibt, die dies auf die eine oder andere Weise tun. Indem wir C # einfach nicht unterstützen, müssen wir uns keine Sorgen mehr machen.

Es ist auch wahrscheinlich, dass ein Compiler, der diesem Wunsch entspricht, einige Optimierungen verliert, die mit einer strengeren Trennung zwischen Instanz- und statischen Methoden einhergehen können.

AnthonyWJones
quelle
Interessanterweise ist es durchaus möglich, eine statische Methode von beiden Type ad Instance in VB.Net aufzurufen (obwohl die IDE im letzteren Fall eine Warnung ausgibt). Es scheint kein Problem zu sein. Sie können mit den Optimierungen Recht haben.
Kramii
3

Sie können sich die statischen und nicht statischen Methoden einer Klasse als unterschiedliche Schnittstellen vorstellen. Beim Aufruf werden statische Methoden in das statische Singleton-Klassenobjekt und nicht statische Methoden in die Instanz der Klasse aufgelöst, mit der Sie sich befassen. Wenn Sie also statische und nicht statische Methoden in einer Schnittstelle verwenden, deklarieren Sie effektiv zwei Schnittstellen, wenn wir wirklich möchten, dass Schnittstellen für den Zugriff auf eine zusammenhängende Sache verwendet werden.

Scott Langham
quelle
Dies ist eine interessante POV und wahrscheinlich die, an die die C # -Designer gedacht hatten. Ich werde statische Mitglieder von nun an anders betrachten.
Kramii
3

Um ein Beispiel zu geben, bei dem mir entweder die statische Implementierung von Schnittstellenmethoden fehlt oder was Mark Brackett als "sogenannte Typmethode" eingeführt hat:

Beim Lesen aus einem Datenbankspeicher verfügen wir über eine generische DataTable-Klasse, die das Lesen aus einer Tabelle einer beliebigen Struktur übernimmt. Alle tabellenspezifischen Informationen werden in eine Klasse pro Tabelle gestellt, die auch Daten für eine Zeile aus der Datenbank enthält und eine IDataRow-Schnittstelle implementieren muss. In der IDataRow ist eine Beschreibung der Struktur der Tabelle enthalten, die aus der Datenbank gelesen werden soll. Die DataTable muss die Datenstruktur in der IDataRow anfordern, bevor sie aus der Datenbank gelesen werden kann. Derzeit sieht das so aus:

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

}

Die GetDataStructure ist nur einmal erforderlich, damit jede Tabelle gelesen werden kann. Der Aufwand für das Instanziieren einer weiteren Instanz ist minimal. In diesem Fall wäre es hier jedoch schön.


quelle
1

Zu Ihrer Information: Sie könnten ein ähnliches Verhalten wie gewünscht erzielen, indem Sie Erweiterungsmethoden für die Schnittstelle erstellen. Die Erweiterungsmethode wäre ein gemeinsames, nicht überschreibbares statisches Verhalten. Leider wäre diese statische Methode nicht Vertragsbestandteil.

Daniel Auger
quelle
1

Schnittstellen sind abstrakte Sätze definierter verfügbarer Funktionen.

Ob sich eine Methode in dieser Schnittstelle statisch verhält oder nicht, ist ein Implementierungsdetail, das hinter der Schnittstelle verborgen werden sollte . Es wäre falsch, eine Schnittstellenmethode als statisch zu definieren, da Sie die Implementierung der Methode auf eine bestimmte Weise unnötig erzwingen würden.

Wenn Methoden als statisch definiert würden, wäre die Klasse, die die Schnittstelle implementiert, nicht so gekapselt, wie sie sein könnte. Kapselung ist eine gute Sache, nach der man im objektorientierten Design streben sollte (ich werde nicht darauf eingehen, warum, das können Sie hier lesen: http://en.wikipedia.org/wiki/Object-oriented ). Aus diesem Grund sind statische Methoden in Schnittstellen nicht zulässig.

Scott Langham
quelle
1
Ja, eine statische Aufladung in der Schnittstellendeklaration wäre dumm. Eine Klasse sollte nicht gezwungen werden, die Schnittstelle auf eine bestimmte Weise zu implementieren. Beschränkt C # eine Klasse jedoch auf die Implementierung der Schnittstelle mit nicht statischen Methoden? Macht das Sinn? Warum?
Kramii
Sie können das Schlüsselwort 'static' nicht verwenden. Es gibt jedoch keine Einschränkung, da Sie das statische Schlüsselwort nicht benötigen, um eine Methode zu schreiben, die sich statisch verhält. Schreiben Sie einfach eine Methode, die auf keines der Mitglieder ihres Objekts zugreift. Dann verhält sie sich wie eine statische Methode. Es gibt also Einschränkungen.
Scott Langham
True Scott, aber es erlaubt niemandem, statisch auf diese Methode zuzugreifen, in einem anderen Teil des Codes. Nicht, dass ich mir einen Grund vorstellen könnte, den Sie möchten, aber das scheint das zu sein, was das OP verlangt.
Chris Marasti-Georg
Wenn Sie wirklich das Bedürfnis haben, können Sie es als statische Methode für den Zugriff in einen anderen Teil des Codes schreiben und einfach die nicht statische Methode schreiben, um die statische Methode aufzurufen. Ich könnte mich irren, aber ich bezweifle, dass dies ein Ziel des Fragestellers ist.
Scott Langham
1

Statische Klassen sollten dies können, damit sie generisch verwendet werden können. Ich musste stattdessen einen Singleton implementieren, um die gewünschten Ergebnisse zu erzielen.

Ich hatte eine Reihe von statischen Business-Layer-Klassen, die CRUD-Methoden wie "Erstellen", "Lesen", "Aktualisieren", "Löschen" für jeden Entitätstyp wie "Benutzer", "Team" usw. implementierten. Dann erstellte ich eine Basis Steuerelement mit einer abstrakten Eigenschaft für die Business Layer-Klasse, die die CRUD-Methoden implementiert hat. Dadurch konnte ich die Operationen "Erstellen", "Lesen", "Aktualisieren" und "Löschen" aus der Basisklasse automatisieren. Ich musste wegen der statischen Einschränkung einen Singleton verwenden.

Louis Rebolloso
quelle
1

Die meisten Leute scheinen zu vergessen, dass es sich bei OOP-Klassen auch um Objekte handelt, und daher haben sie Nachrichten, die c # aus irgendeinem Grund "statische Methode" nennt. Die Tatsache, dass Unterschiede zwischen Instanzobjekten und Klassenobjekten bestehen, zeigt nur Fehler oder Mängel in der Sprache. Optimist über c # obwohl ...

Mar Bar
quelle
2
Das Problem ist, dass es bei dieser Frage nicht um "in OOP" geht. Es geht um "in C #". In C # gibt es keine "Nachrichten".
John Saunders
1

OK, hier ist ein Beispiel für die Notwendigkeit einer 'Typmethode'. Ich erstelle eine aus einer Reihe von Klassen, die auf Quell-XML basieren. Also habe ich eine

  static public bool IsHandled(XElement xml)

Funktion, die wiederum für jede Klasse aufgerufen wird.

Die Funktion sollte statisch sein, da wir sonst Zeit damit verschwenden, unangemessene Objekte zu erstellen. Wie @Ian Boyde betont, könnte dies in einer Fabrikklasse erfolgen, dies erhöht jedoch nur die Komplexität.

Es wäre schön, es der Schnittstelle hinzuzufügen, um Klassenimplementierer zur Implementierung zu zwingen. Dies würde keinen signifikanten Overhead verursachen - es handelt sich lediglich um eine Überprüfung der Kompilierungs- / Verknüpfungszeit und hat keinen Einfluss auf die vtable.

Es wäre jedoch auch eine eher geringfügige Verbesserung. Da die Methode statisch ist, muss ich als Aufrufer sie explizit aufrufen und erhalte sofort einen Kompilierungsfehler, wenn sie nicht implementiert ist. Das Zulassen, dass es auf der Schnittstelle angegeben wird, würde bedeuten, dass dieser Fehler geringfügig früher im Entwicklungszyklus auftritt. Dies ist jedoch im Vergleich zu anderen Problemen mit defekten Schnittstellen trivial.

Es handelt sich also um ein geringfügiges potenzielles Merkmal, das unter dem Strich wahrscheinlich am besten weggelassen wird.

Stephen Westlake
quelle
1

Die Tatsache, dass eine statische Klasse in C # von Microsoft implementiert wird, indem eine spezielle Instanz einer Klasse mit den statischen Elementen erstellt wird, ist nur eine Seltsamkeit, wie statische Funktionalität erreicht wird. Es ist kein theoretischer Punkt.

Eine Schnittstelle SOLLTE ein Deskriptor der Klassenschnittstelle sein - oder wie mit ihr interagiert wird, und dies sollte statische Interaktionen einschließen. Die allgemeine Definition der Schnittstelle (von Meriam-Webster): der Ort oder Bereich, an dem sich verschiedene Dinge treffen und miteinander kommunizieren oder sich gegenseitig beeinflussen. Wenn Sie statische Komponenten einer Klasse oder statische Klassen vollständig weglassen, ignorieren wir große Teile der Interaktion dieser bösen Jungs.

Hier ist ein sehr klares Beispiel dafür, wo die Verwendung von Schnittstellen mit statischen Klassen sehr nützlich wäre:

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

Derzeit schreibe ich die statischen Klassen, die diese Methoden enthalten, ohne zu überprüfen, ob ich nichts vergessen habe. Ist wie die schlechten alten Zeiten der Programmierung vor OOP.

Thomas Phaneuf
quelle
Dies ist eine alte Frage; Heutzutage bemühen wir uns sehr, meinungsbasierte Fragen zu vermeiden, da sie nur schwer autoritativ zu beantworten sind. Die einzige gute Möglichkeit, dies zu beantworten, besteht darin, die Gründe zu beschreiben, aus denen MS dies nachweislich getan hat oder hätte tun müssen.
Nathan Tuggy
1

C # und die CLR sollten wie Java statische Methoden in Schnittstellen unterstützen. Der statische Modifikator ist Teil einer Vertragsdefinition und hat eine Bedeutung, insbesondere, dass das Verhalten und der Rückgabewert nicht je nach Instanz variieren, obwohl er von Anruf zu Anruf variieren kann.

Ich empfehle jedoch, stattdessen eine Anmerkung zu verwenden, wenn Sie eine statische Methode in einer Schnittstelle verwenden möchten und dies nicht können. Sie erhalten die Funktionalität, die Sie suchen.

Daniel Barbalace
quelle
0

Ich denke, die kurze Antwort lautet "weil es keinen Nutzen hat". Um eine Schnittstellenmethode aufzurufen, benötigen Sie eine Instanz des Typs. Über Instanzmethoden können Sie beliebige statische Methoden aufrufen.

Mackenir
quelle
0

Ich denke, die Frage ist, dass C # für genau diese Art von Situation ein anderes Schlüsselwort benötigt. Sie möchten eine Methode, deren Rückgabewert nur vom Typ abhängt, für den sie aufgerufen wird. Sie können es nicht "statisch" nennen, wenn der Typ unbekannt ist. Sobald der Typ bekannt ist, wird er statisch. "Ungelöste statische Aufladung" ist die Idee - sie ist noch nicht statisch, aber sobald wir den empfangenden Typ kennen, wird es so sein. Dies ist ein perfektes Konzept, weshalb Programmierer immer wieder danach fragen. Aber es passte nicht ganz in die Art und Weise, wie die Designer über die Sprache dachten.

Da es nicht verfügbar ist, habe ich mich daran gemacht, nicht statische Methoden wie unten gezeigt zu verwenden. Nicht gerade ideal, aber ich sehe keinen sinnvolleren Ansatz, zumindest nicht für mich.

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

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

Gemäß objektorientiertem Konzept Schnittstelle, die von Klassen implementiert wird und über einen Vertrag verfügt, um über Objekt auf diese implementierten Funktionen (oder Methoden) zuzugreifen.

Wenn Sie also auf Schnittstellenvertragsmethoden zugreifen möchten, müssen Sie ein Objekt erstellen. Bei statischen Methoden ist dies immer nicht zulässig. Statische Klassen, Methoden und Variablen erfordern niemals Objekte und werden in den Speicher geladen, ohne ein Objekt dieses Bereichs (oder dieser Klasse) zu erstellen, oder Sie können sagen, dass keine Objekterstellung erforderlich ist.

Sanjeev Saraswat
quelle
0

Konzeptionell gibt es keinen Grund, warum eine Schnittstelle keinen Vertrag definieren könnte, der statische Methoden enthält.

Bei der aktuellen Implementierung der C # -Sprache ist die Einschränkung auf die Berücksichtigung der Vererbung einer Basisklasse und von Schnittstellen zurückzuführen. Wenn "Klasse SomeBaseClass" "Schnittstelle ISomeInterface" und "Klasse SomeDerivedClass: SomeBaseClass, ISomeInterface" ebenfalls die Schnittstelle implementiert, schlägt eine statische Methode zum Implementieren einer Schnittstellenmethode fehl, da eine statische Methode nicht dieselbe Signatur wie eine Instanzmethode haben kann (was der Fall wäre) in der Basisklasse vorhanden sein, um die Schnittstelle zu implementieren).

Eine statische Klasse ist funktional identisch mit einem Singleton und dient demselben Zweck wie ein Singleton mit sauberer Syntax. Da ein Singleton eine Schnittstelle implementieren kann, sind statische Schnittstellenimplementierungen konzeptionell gültig.

Es läuft also einfach auf die Begrenzung des C # -Namenkonflikts und der gleichnamigen statischen Methoden bei der Vererbung hinaus. Es gibt keinen Grund, warum C # nicht "aktualisiert" werden konnte, um statische Methodenverträge (Schnittstellen) zu unterstützen.

Greg McPherran
quelle
-1

Wenn eine Klasse eine Schnittstelle implementiert, erstellt sie eine Instanz für die Schnittstellenmitglieder. Während ein statischer Typ keine Instanz hat, macht es keinen Sinn, statische Signaturen in einer Schnittstelle zu haben.

Vinay Chanumolu
quelle