Frage Werden Erweiterungsmethoden für Schnittstellen als weniger prioritär behandelt als weniger spezifische?


Ich habe folgende Erweiterungsklasse:

public static class MatcherExtensions
{
    public static ExecMatcher<T1, T2> Match<T1, T2>(this Tuple<T1, T2> item)
    {
        return new ExecMatcher<T1, T2>(item.Item1, item.Item2);
    }

    public static ExecMatcher<T1, T2> Match<T1, T2>(this ITupleMatchable<T1, T2> item)
    {
        var tuple = item.PropertiesToMatch;
        return new ExecMatcher<T1, T2>(tuple.Item1, tuple.Item2);
    }

    public static ExecMatcher<T> Match<T>(this T item) { return new ExecMatcher<T>(item); }
}

Wenn ich ein Tupel erstelle und aufrufe Match(), verwendet es korrekt die erste Erweiterungsmethode:

var tuple = Tuple.Create(1, "a");
tuple.Match().With(1, "a")...   // compiles just fine.

Wenn ich ein int erstellen und aufrufen Match(), verwendet es korrekt die letzte Erweiterungsmethode:

var one = 1;
one.Match().With(1)... // compiles just fine.

Wenn ich jedoch erstelle SomeClass, die implementiert ITupleMatchable<int, string> und versuchen Sie und passen Sie darauf an, wählt der Compiler immer noch die dritte Erweiterungsmethode statt der zweiten, obwohl letztere eine spezifischere Übereinstimmung ist:

var obj = new SomeClass(1, "a");
obj.Match().With(1, "a")... // won't compile.

Beyogen auf Eric Lipperts Antwort auf eine ähnliche FrageIch habe daran gearbeitet, indem ich die dritte Erweiterungsmethode in eine eigene Klasse innerhalb eines Unterverzeichnisses eingefügt habe, wodurch ein längerer Namespace und damit mehr "Abstand" zwischen ihm und dem aufrufenden Code als für die spezifische Version erzeugt wurde ITupleMatchable<T1, T2>. Das fühlt sich für mich wie ein Hack an. Gibt es eine bessere Lösung?


11
2017-07-10 16:27


Ursprung


Antworten:


Wenn Sie einfach casten new SomeClass(1, "a") zu ITupleMatchable<int, string>, es wird gut funktionieren:

var obj = (ITupleMatchable<int, string>)new SomeClass(1, "a");
obj.Match().With(1, "a");

Denken Sie daran, dass Ihr obj Variable hat ansonsten einen Kompilierungszeittyp von SomeClass. Der Compiler kann "einfacher" die tatsächliche Klasse mit der dritten Erweiterungsmethode (die mit irgendein type), als wenn man sich die Schnittstellenimplementierungen von SomeClass und dann wird es z. die zweite Erweiterungsmethode.

Aber wenn Sie das zur Verfügung stellen this Parameter als der tatsächliche Schnittstellentyp, dann ist die zweite Erweiterungsmethode besser geeignet, weil es genau der Typ ist, nach dem die Methode sucht, anstatt der breitere "any type" zu sein. I.e. es ist eine spezifischere Erklärung und somit "besser".

Beachten Sie, dass, sobald der Kandidatensatz von Erweiterungsmethoden gefunden wurde (über Regeln, die sich auf den Namensraum usw. beziehen), die tatsächliche Methode unter Verwendung der normalen Überladungsauflösung bestimmt wird. I.e. festgestellt, dass mindestens eine Methode in Ihrem MatcherExtensions Klasse ist eine zulässige Erweiterungsmethode, der Compiler geht dann mit den normalen Überladungsauflösungsregeln, um unter diesen auszuwählen. Sie finden diese Regeln im Abschnitt C # 5.0-Spezifikation 7.5.3.

Kurz gesagt: Bevor Sie die Regeln für die Überladungsauflösung anwenden (um festzustellen, welche Methoden überhaupt zulässig sind), beachten Sie, dass der Compiler bereits die generischen Typparameter festgelegt hat. Also, wie es die Überladungsauflösung auswertet, schaut es sich an Match(SomeClass item) und Match(ITupleMatchable<int, string> item). Hoffentlich werden Sie sehen, warum, wenn die Variable den Typ hat SomeClass, wählt der Compiler Ihre dritte Erweiterung bevorzugt über die zweite und umgekehrt, wenn der Typ ist ITupleMatchable<int, string>.


3
2017-07-10 16:56