Gibt es in C # eine Möglichkeit, eine Klassenmethode mit einer Erweiterungsmethode zu überschreiben?

98

Es gab Fälle, in denen ich eine Methode in einer Klasse mit einer Erweiterungsmethode überschreiben wollte. Gibt es eine Möglichkeit, dies in C # zu tun?

Beispielsweise:

public static class StringExtension
{
    public static int GetHashCode(this string inStr)
    {
        return MyHash(inStr);
    }
}

Ein Fall, in dem ich dies tun wollte, besteht darin, einen Hash einer Zeichenfolge in einer Datenbank speichern zu können und denselben Wert von allen Klassen verwenden zu lassen, die den Hash der Zeichenfolgenklasse verwenden (z. B. Wörterbuch usw.) Es ist nicht garantiert, dass der integrierte .NET-Hashing-Algorithmus von einer Version des Frameworks zur nächsten kompatibel ist. Ich möchte ihn durch meine eigene ersetzen.

Es gibt andere Fälle, in denen ich eine Klassenmethode mit einer Erweiterungsmethode überschreiben möchte, damit sie nicht nur für die Zeichenfolgenklasse oder die GetHashCode-Methode spezifisch ist.

Ich weiß, dass ich dies mit einer Unterklasse einer vorhandenen Klasse tun könnte, aber es wäre in vielen Fällen praktisch, dies mit einer Erweiterung tun zu können.

Phred Menyhert
quelle
Da ein Wörterbuch eine speicherinterne Datenstruktur ist, welchen Unterschied macht es, ob sich der Hashing-Algorithmus von einer Version des Frameworks zur nächsten ändert? Wenn sich die Framework-Version ändert, wurde die Anwendung offensichtlich neu gestartet und das Wörterbuch neu erstellt.
David Nelson

Antworten:

91

Nein; Eine Erweiterungsmethode hat niemals Vorrang vor einer Instanzmethode mit einer geeigneten Signatur und nimmt niemals am Polymorphismus teil ( GetHashCodeist eine virtualMethode).

Marc Gravell
quelle
"... nimmt niemals am Polymorphismus teil (GetHashCode ist eine virtuelle Methode)" Könnten Sie auf andere Weise erklären, warum eine Erweiterungsmethode niemals am Polymorphismus teilnehmen würde, weil sie virtuell ist? Ich habe Probleme, den Zusammenhang mit diesen beiden Aussagen zu erkennen.
Alex
3
@Alex durch Erwähnung von virtuell erkläre ich einfach, was es bedeutet, polymorph zu sein. Bei praktisch allen Anwendungen von GetHashCode ist der konkrete Typ unbekannt - daher spielt Polymorphismus eine Rolle. Daher würden Erweiterungsmethoden nicht helfen, selbst wenn sie im regulären Compiler Priorität hätten . Was das OP wirklich will, ist das Patchen von Affen. Welche c # und .net nicht erleichtern.
Marc Gravell
4
Das ist traurig, in C # so viele irreführende unsinnige Methoden, wie zB .ToString()in Arrays. Es ist definitiv eine notwendige Funktion.
Hi-Angel
Warum erhalten Sie dann keinen Compilerfehler? ZB tippe ich. namespace System { public static class guilty { public static string ToLower(this string s) { return s.ToUpper() + " I'm Malicious"; } } }
LowLevel
@ LowLevel warum würdest du?
Marc Gravell
7

Wenn die Methode eine andere Signatur hat, kann dies durchgeführt werden - also in Ihrem Fall: Nein.

Andernfalls müssen Sie die Vererbung verwenden, um das zu tun, wonach Sie suchen.

Chris Brandsma
quelle
2

Soweit ich weiß, lautet die Antwort nein, da eine Erweiterungsmethode keine Instanz ist. Für mich ist es eher eine Intellisense-Funktion, mit der Sie eine statische Methode mit einer Instanz einer Klasse aufrufen können. Ich denke, eine Lösung für Ihr Problem kann ein Interceptor sein, der die Ausführung einer bestimmten Methode (z. B. GetHashCode ()) abfängt und etwas anderes tut. Um einen solchen Interceptor zu verwenden (wie der, den Castle Project bereitstellt), sollten alle Objekte mit einem instanziiert werden Object Factory (oder ein IoC-Container in Castle), damit ihre Schnittstellen über einen zur Laufzeit generierten dynamischen Proxy abgefangen werden können. (Mit Caslte können Sie auch virtuelle Mitglieder von Klassen abfangen.)

Beatles1692
quelle
0

Ich habe einen Weg gefunden, eine Erweiterungsmethode mit derselben Signatur wie eine Klassenmethode aufzurufen, aber sie scheint nicht sehr elegant zu sein. Beim Herumspielen mit Erweiterungsmethoden bemerkte ich ein undokumentiertes Verhalten. Beispielcode:

public static class TestableExtensions
{
    public static string GetDesc(this ITestable ele)
    {
        return "Extension GetDesc";
    }

    public static void ValDesc(this ITestable ele, string choice)
    {
        if (choice == "ext def")
        {
            Console.WriteLine($"Base.Ext.Ext.GetDesc: {ele.GetDesc()}");
        }
        else if (choice == "ext base" && ele is BaseTest b)
        {
            Console.WriteLine($"Base.Ext.Base.GetDesc: {b.BaseFunc()}");
        }
    }

    public static string ExtFunc(this ITestable ele)
    {
        return ele.GetDesc();
    }

    public static void ExtAction(this ITestable ele, string choice)
    {
        ele.ValDesc(choice);
    }
}

public interface ITestable
{

}

public class BaseTest : ITestable
{
    public string GetDesc()
    {
        return "Base GetDesc";
    }

    public void ValDesc(string choice)
    {
        if (choice == "")
        {
            Console.WriteLine($"Base.GetDesc: {GetDesc()}");
        }
        else if (choice == "ext")
        {
            Console.WriteLine($"Base.Ext.GetDesc: {this.ExtFunc()}");
        }
        else
        {
            this.ExtAction(choice);
        }
    }

    public string BaseFunc()
    {
        return GetDesc();
    }
}

Was mir auffiel, war, dass wenn ich eine zweite Methode innerhalb einer Erweiterungsmethode aufrief, diese die Erweiterungsmethode aufrief, die mit der Signatur übereinstimmte, selbst wenn es eine Klassenmethode gab, die auch mit der Signatur übereinstimmte. Wenn ich beispielsweise im obigen Code ExtFunc () aufrufe, das wiederum ele.GetDesc () aufruft, erhalte ich die Rückgabezeichenfolge "Extension GetDesc" anstelle der erwarteten Zeichenfolge "Base GetDesc".

Testen des Codes:

var bt = new BaseTest();
bt.ValDesc("");
//Output is Base.GetDesc: Base GetDesc
bt.ValDesc("ext");
//Output is Base.Ext.GetDesc: Extension GetDesc
bt.ValDesc("ext def");
//Output is Base.Ext.Ext.GetDesc: Extension GetDesc
bt.ValDesc("ext base");
//Output is Base.Ext.Base.GetDesc: Base GetDesc

Auf diese Weise können Sie nach Belieben zwischen Klassenmethoden und Erweiterungsmethoden hin- und herwechseln, müssen jedoch doppelte "Pass-Through" -Methoden hinzufügen, um in den gewünschten "Bereich" zu gelangen. Ich nenne es hier Spielraum, weil es kein besseres Wort gibt. Hoffentlich kann mich jemand wissen lassen, wie es eigentlich heißt.

Vielleicht haben Sie anhand meiner "Pass-Through" -Methodennamen erraten, dass ich auch mit der Idee gespielt habe, Delegierte an sie weiterzugeben, in der Hoffnung, dass eine oder zwei Methoden als Pass-Through für mehrere Methoden mit derselben Signatur fungieren könnten. Leider sollte es nicht so sein, dass der Delegat nach dem Entpacken immer die Klassenmethode gegenüber der Erweiterungsmethode auswählte, selbst innerhalb einer anderen Erweiterungsmethode. "Umfang" spielte keine Rolle mehr. Ich habe Action- und Func-Delegierte allerdings nicht sehr oft eingesetzt, sodass vielleicht jemand mit mehr Erfahrung diesen Teil herausfinden könnte.

DanK
quelle