wake-up-neo.net

Warum muss diese Schnittstelle explizit implementiert werden?

Nach ein paar Jahren komme ich wieder zu C #, also bin ich ein bisschen rostig. Kam durch diesen (vereinfachten) Code und ließ meinen Kopf kratzen.

Warum müssen Sie die IDataItem.Children-Eigenschaft explizit implementieren? Erfüllt die normale Children-Eigenschaft die Anforderung nicht? Schließlich wird die Immobilie direkt zur Befriedigung genutzt. Warum ist es nicht implizit?

public interface IDataItem {

    IEnumerable<string> Children { get; }
}

public class DataItem : IDataItem {

    public Collection<string> Children { get; } = new Collection<string>();

    // Why doesn't 'Children' above implement this automatically?!
    // After all it's used directly to satisfy the requirement!
    IEnumerable<string> IDataItem.Children => Children;
}

Gemäß der C # -Quelle ist hier die Definition von Collection<T>:

[System.Runtime.InteropServices.ComVisible(false)]
public class Collection<T> :
    System.Collections.Generic.ICollection<T>,
    System.Collections.Generic.IEnumerable<T>, <-- Right Here
    System.Collections.Generic.IList<T>,
    System.Collections.Generic.IReadOnlyCollection<T>,
    System.Collections.Generic.IReadOnlyList<T>,
    System.Collections.IList

Wie Sie sehen, implementiert es explizit IEnumerable<T> und wenn meines Erachtens "X" Y implementiert, dann ist "X" ein "Y", also ist Collection<String> ein IEnumerable<String> sicher, warum es nicht zufrieden ist.

12
MarqueIV

Vielleicht macht dieses Beispiel es klarer. Wir möchten, dass die Signaturen mit genau übereinstimmen1,2Trotz Vererbungsbeziehungen zwischen den Typen sind keine Ersetzungen zulässig.

Das dürfen wir nicht schreiben:

public interface IDataItem {

    void DoStuff(string value);
}

public class DataItem : IDataItem {

    public void DoStuff(object value) { }
}

Ihr Beispiel ist das gleiche, mit der Ausnahme, dass Sie nach Rückgabetypen anstatt nach Parametern fragen (und aus offensichtlichen Gründen eine Verengung anstelle einer Ausweitung der Konvertierung verwenden). Gleichwohl gilt derselbe Grundsatz. Wenn es um übereinstimmende Signaturen geht, müssen die Typen mit genau übereinstimmen3.

Sie können nach einer Sprache fragen, in der solche Dinge möglich sind und solche Sprachen möglicherweise existieren. Tatsache ist jedoch, dass dies die Regeln von C # sind.


1Außerhalb einer begrenzten Unterstützung für Co- und Contra-Varianz einschließlich Generika und Interfaces/Delgates.

2Einige mögen darüber streiten, ob Signatur das richtige Wort ist, da in diesem Fall Rückgabetypen genauso wichtig sind wie Parametertypen, generische Arität usw .; In den meisten anderen Situationen, in denen jemand über C # -Methodensignaturen spricht, ignoriert er explizit den Rückgabetyp, da er (explizit oder implizit) überlegt, was die Überladung -Regeln sagen, und zum Überladen, Rückgabetypen sind nicht Bestandteil der Signatur.

Trotzdem bin ich mit meiner Verwendung des Wortes "Signatur" hier zufrieden. Signaturen sind in der C # -Spezifikation nicht formal definiert, und wo sie verwendet werden, wird häufig darauf hingewiesen, welche Teile der Signatur sind es nicht zum Überladen berücksichtigt werden müssen.

3Ganz zu schweigen von den Problemen, die auftreten würden, wenn Ihre Children -Methode ein struct zurückgibt, das zufällig IEnumerable<string> Implementiert hat. Jetzt haben Sie eine Methode, die den Wert eines Wertetyps und einen Aufrufer zurückgibt (über die Schnittstelle IDataItem, die einen Verweis auf ein Objekt erwartet.

Es ist also nicht einmal so, dass die Methode so wie sie ist verwendet werden könnte. Wir müssten (in diesem Fall) hidden Boxing-Konvertierungen durchführen, um die Schnittstelle zu implementieren. Als dieser Teil von C # beschrieben wurde, versuchten sie, glaube ich, nicht zu viel "versteckte Magie" für Code zu haben, den Sie leicht selbst schreiben konnten.

In Ihrem Beispiel erfüllt Ihre "normale" Children-Eigenschaft die Schnittstellenanforderung nicht wirklich. Der Typ ist anders. Es ist nicht wirklich wichtig, dass Sie es wirken können - sie sind unterschiedlich.

Ein ähnliches Beispiel und vielleicht etwas offensichtlicher ist es, wenn Sie eine Schnittstelle mit einer tatsächlichen Methode implementieren, die IEnumerable zurückgibt und eine ICollection-Methode aus der tatsächlichen Klasse ausprobiert. Es gibt immer noch diesen Fehler beim Kompilieren.

Wie @Ben Voigt sagte, die Konvertierung erzeugt immer noch Code, und wenn Sie wollen, müssen Sie ihn implizit hinzufügen.

5
JleruOHeP

Die Frage wurde hier beantwortet (Sie müssen den Schnittstellentypen entsprechen) und wir können das Problem an einem Beispiel demonstrieren. Wenn das funktioniert hat:

public interface IDataItem {

    IEnumerable<string> Children { get; set; }

    void Wipe();
}

public class DataItem : IDataItem {

    public Collection<string> Children { get; } = new Collection<string>(); 

    public void Wipe() {
        Children.ClearItems();  //Property exists on Collection, but not on IEnumerable
    }
}

Und wir verwenden diesen Code dann so:

IDataItem x = new DataItem();

//Should be legal, as Queue implements IEnumerable. Note entirely sure
//how this would work, but this is the system you are asking about.
x.Children = new Queue<string>();

x.Wipe();  // Will fail, as Queue does not have a ClearItems method within

Entweder meinen Sie, dass die Eigenschaft immer nur aufgezählt werden kann, oder Sie benötigen die Eigenschaften der Collection-Klasse.

0
Paddy