2 Stimmen

Verhalten von Erweiterungsmethoden gegenüber Methoden der übergeordneten Klasse

Sehen Sie sich den folgenden Code an:

class A
{
    public string DoSomething(string str)
    {
        return "A.DoSomething: " + str;
    }
}

class B : A
{
}

static class BExtensions
{
    public static string DoSomething(this B b, string str)
    {
        return "BExtensions.DoSomething: " + str;
    }
}

class Program
{
    static void Main(string[] args)
    {
        var a = new A();
        var b = new B();
        Console.WriteLine(a.DoSomething("test"));
        Console.WriteLine(b.DoSomething("test"));

        Console.ReadKey();
    }
}

Die Ausgabe des Codes ist:

A.DoSomething: test

A.DoSomething: test

Beim Kompilieren gibt es keine Warnungen.

Meine Fragen sind: Warum gibt es keine Warnungen, wenn dieser Code kompiliert und was genau passiert, wenn die DoSomething Methode aufgerufen wird?

6voto

Anton Gogolev Punkte 109749

Was passiert, wenn die Methode aufgerufen wird, ist einfach: ein Aufruf der Instanzmethode. Da C# early-bound ist, werden alle Methoden zur Kompilierzeit aufgelöst. Außerdem werden Instanzmethoden gegenüber Erweiterungsmethoden bevorzugt, weshalb Ihre Erweiterungsmethode nie aufgerufen wird.

Véase este :

Sie können Erweiterungsmethoden verwenden, um eine Klasse oder Schnittstelle zu erweitern, aber nicht, um sie zu überschreiben. Eine Erweiterungsmethode mit demselben Namen und derselben Signatur wie eine Schnittstellen- oder Klassenmethode wird niemals aufgerufen. Zur Kompilierzeit haben Erweiterungsmethoden immer eine niedrigere Priorität als die im Typ selbst definierten Instanzmethoden.

Mit anderen Worten: Wenn ein Typ eine Methode namens Process(int i) und Sie haben eine Erweiterungsmethode mit der gleichen Signatur, wird der Compiler immer an die Instanzmethode binden. Wenn der Compiler auf einen Methodenaufruf stößt, sucht er zunächst nach einer Übereinstimmung in den Instanzmethoden des Typs. Wenn keine Übereinstimmung gefunden wird, sucht er nach allen Erweiterungsmethoden, die für den Typ definiert sind, und bindet an die erste gefundene Erweiterungsmethode.

4voto

Jon Skeet Punkte 1325502

Grundsätzlich wird der Compiler immer eine Instanzmethode verwenden, wenn sie verfügbar ist, und nur dann auf Erweiterungsmethoden zurückgreifen, wenn alles andere versagt. Aus Abschnitt 7.5.5.2 der C# 3.0 Spezifikation:

Bei einem Methodenaufruf (§7.5.5.1) von einer der Formen

  • expr . Bezeichner ( )
  • expr . identifier ( args )
  • expr . identifier < typeargs > ( )
  • expr . identifier < typeargs > ( args )

wenn die normale Verarbeitung der Aufrufs keine anwendbaren Methoden findet, wird versucht, das das Konstrukt als eine Erweiterungsmethode zu verarbeiten aufzurufen.

Dies ist eines meiner Probleme mit der Art und Weise, wie Erweiterungsmethoden gefunden werden... die Erweiterungsmethode DoSomething wird nunca als Erweiterungsmethode aufgerufen werden (obwohl sie mit normaler statischer Methodensyntax aufrufbar ist)... und dennoch gibt der Compiler nicht einmal eine Warnung aus :(

1voto

Oleg I. Punkte 783

Vielleicht bin ich nicht ganz korrekt, aber der Compiler macht etwas Ähnliches: Wenn er zu

b.DoSomething("test")

Es wird versucht, eine Methode mit derselben Signatur in dieser oder in der Basisklasse zu finden, und wenn es die Methode in der Basisklasse findet, wird der Aufruf darauf abgebildet und die Erweiterungsmethode einfach ignoriert.

Wenn Sie jedoch beispielsweise die Basisklasse A aus der Deklaration von B in derselben Zeile entfernen, prüft der Compiler, dass in dieser oder der Basisklasse keine Methode mit einer solchen Signatur existiert, und ersetzt sie durch den Aufruf der statischen Methode BExtensions.DoSomething.

Sie können es mit .NET Reflector überprüfen.

Wenn sich B von A ableitet:

.locals init (
    [0] class Test.A a,
    [1] class Test.B b)
...
ldloc.1 // loading local variable b
ldstr "test"
callvirt instance string Test.A::DoSomething(string)
call void [mscorlib]System.Console::WriteLine(string)

Wenn B von System.Object abgeleitet ist:

.locals init (
    [0] class Test.A a,
    [1] class Test.B b)
...
ldloc.1 // loading local variable b
ldstr "test"
call string Test.BExtensions::DoSomething(class Test.B, string)
call void [mscorlib]System.Console::WriteLine(string)

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X