Warum gibt der C # -Compiler keinen Fehlercode aus, wenn eine statische Methode eine Instanzmethode aufruft?

110

Der folgende Code hat eine statische Methode Foo(), die eine Instanzmethode aufruft Bar():

public sealed class Example
{
    int count;

    public static void Foo( dynamic x )
    {
        Bar(x);
    }

    void Bar( dynamic x )
    {
        count++;
    }
}

Es wird ohne Fehler kompiliert *, generiert jedoch zur Laufzeit eine Laufzeitbinder-Ausnahme. Das Entfernen des dynamischen Parameters zu diesen Methoden führt erwartungsgemäß zu einem Compilerfehler.

Warum kann der Code mit einem dynamischen Parameter kompiliert werden? ReSharper zeigt es auch nicht als Fehler an.

Bearbeiten Sie 1: * in Visual Studio 2008

Bearbeiten 2: hinzugefügt, sealedda es möglich ist, dass eine Unterklasse eine statische Bar(...)Methode enthält. Selbst die versiegelte Version wird kompiliert, wenn es nicht möglich ist, dass zur Laufzeit eine andere Methode als die Instanzmethode aufgerufen wird.

Mike Scott
quelle
8
+1 für sehr gute Frage
Cuongle
40
Dies ist eine Eric-Lippert-Frage.
Olivier Jacot-Descombes
3
Ich bin mir ziemlich sicher, dass Jon Skeet auch wissen würde, was er damit anfangen soll;) @ OlivierJacot-Descombes
Tausend
2
@Olivier, Jon Skeet wollte wahrscheinlich, dass der Code kompiliert wird, also erlaubt der Compiler es :-))
Mike Scott
5
Dies ist ein weiteres Beispiel dafür, warum Sie es nicht verwenden sollten, es dynamicsei denn, Sie müssen es wirklich.
Servy

Antworten:

71

UPDATE: Die folgende Antwort wurde 2012 vor der Einführung von C # 7.3 (Mai 2018) verfasst . In Was ist neu in C # 7.3 , Abschnitt Verbesserte Überlastungskandidaten , Punkt 1, wird erläutert, wie sich die Regeln für die Überlastungsauflösung geändert haben, sodass nicht statische Überladungen vorzeitig verworfen werden. Die folgende Antwort (und diese ganze Frage) hat also mittlerweile meist nur noch historisches Interesse!


(Vor C # 7.3 :)

Aus irgendeinem Grund findet die Überlastungsauflösung immer die beste Übereinstimmung, bevor nach statischen und nicht statischen Werten gesucht wird. Bitte versuchen Sie diesen Code mit allen statischen Typen:

class SillyStuff
{
  static void SameName(object o) { }
  void SameName(string s) { }

  public static void Test()
  {
    SameName("Hi mom");
  }
}

Dies wird nicht kompiliert, da die beste Überlastung diejenige ist, die a nimmt string. Aber hey, das ist eine Instanzmethode, also beschwert sich der Compiler (anstatt die zweitbeste Überladung zu nehmen).

Ergänzung: Ich denke, die Erklärung für das dynamicBeispiel der ursprünglichen Frage lautet, dass wir, um konsistent zu sein, bei dynamischen Typen auch zuerst die beste Überlastung finden (nur Parameternummer und Parametertypen usw. prüfen, nicht statisch oder nicht -static) und erst dann auf statische Aufladung prüfen. Das bedeutet aber, dass die statische Prüfung bis zur Laufzeit warten muss. Daher das beobachtete Verhalten.

Später Zusatz: Einige Hintergrundinformationen darüber, warum sie sich für diese lustige Reihenfolge entschieden haben, können diesem Blog-Beitrag von Eric Lippert entnommen werden .

Jeppe Stig Nielsen
quelle
Die ursprüngliche Frage enthält keine Überladungen. Die Antworten, die eine statische Überlastung anzeigen, sind nicht relevant. Es ist nicht gültig zu antworten "gut, wenn du das geschrieben hast ...", da ich das nicht geschrieben habe :-)
Mike Scott
5
@MikeScott Ich versuche nur, Sie davon zu überzeugen, dass die Überlastungsauflösung in C # immer so aussieht: (1) Finden Sie die beste Übereinstimmung, ohne statische / nicht statische. (2) Jetzt wissen wir, welche Überlastung zu verwenden ist, und prüfen dann, ob sie statisch ist. Aus diesem Grund haben dynamicdie Designer von C # , als es in die Sprache eingeführt wurde, gesagt: "Wir werden (2) Kompilierungszeit nicht berücksichtigen, wenn es sich um einen dynamicAusdruck handelt." Mein Ziel hier ist es, eine Idee zu finden, warum sie sich entschieden haben, erst zur Laufzeit nach statischen oder instanziellen Daten zu suchen. Ich würde sagen, diese Überprüfung erfolgt zum Zeitpunkt der Bindung .
Jeppe Stig Nielsen
Fair genug, aber es erklärt immer noch nicht, warum in diesem Fall der Compiler den Aufruf der Instanzmethode nicht auflösen kann. Mit anderen Worten, die Art und Weise, wie der Compiler die Auflösung ausführt, ist simpel - er erkennt den einfachen Fall wie in meinem Beispiel nicht, in dem es keine Möglichkeit gibt, den Aufruf nicht aufzulösen. Die Ironie ist: Wenn der Compiler eine einzelne Bar () -Methode mit einem dynamischen Parameter hat, ignoriert er diese einzelne Bar () -Methode.
Mike Scott
45
Ich habe diesen Teil des C # -Compilers geschrieben, und Jeppe hat recht. Bitte stimmen Sie ab. Die Überlastungsauflösung erfolgt vor der Überprüfung, ob eine bestimmte Methode eine statische oder eine Instanzmethode ist. In diesem Fall verschieben wir die Überlastungsauflösung auf die Laufzeit und daher auch die statische / Instanzprüfung bis zur Laufzeit. Darüber hinaus bemüht sich der Compiler nach besten Kräften, dynamische Fehler statisch zu finden, was absolut nicht umfassend ist.
Chris Burrows
30

Foo hat einen Parameter "x", der dynamisch ist, was bedeutet, dass Bar (x) ein dynamischer Ausdruck ist.

Es wäre durchaus möglich, dass Example Methoden wie:

static Bar(SomeType obj)

In diesem Fall würde die richtige Methode aufgelöst, sodass die Anweisung Bar (x) vollkommen gültig ist. Die Tatsache, dass es eine Instanzmethode Bar (x) gibt, spielt keine Rolle und wird nicht einmal berücksichtigt: Da Bar (x) ein dynamischer Ausdruck ist, haben wir per Definition die Auflösung auf die Laufzeit verschoben.

Marc Gravell
quelle
14
Wenn Sie jedoch die Instanz-Bar-Methode entfernen, wird sie nicht mehr kompiliert.
Justin Harvey
1
@ Justin interessant - eine Warnung? Oder ein Fehler? In beiden Fällen wird möglicherweise nur bis zur Methodengruppe validiert, sodass die vollständige Überlastungsauflösung der Laufzeit überlassen bleibt.
Marc Gravell
1
@Marc, da es keine andere Bar () -Methode gibt, beantworten Sie die Frage nicht. Können Sie dies erklären, da es nur eine Bar () -Methode ohne Überladungen gibt? Warum auf die Laufzeit verschieben, wenn keine andere Methode aufgerufen werden kann? Oder ist da? Hinweis: Ich habe den Code bearbeitet, um die Klasse zu versiegeln, die noch kompiliert wird.
Mike Scott
1
@mike als warum auf Laufzeit verschieben: weil das ist, was Dynamik bedeutet
Marc Gravell
2
@ Mike unmöglich ist nicht der Punkt; Wichtig ist, ob es erforderlich ist . Der ganze Punkt bei Dynamic ist, dass es nicht die Aufgabe des Compilers ist.
Marc Gravell
9

Der "dynamische" Ausdruck wird zur Laufzeit gebunden. Wenn Sie also eine statische Methode mit der richtigen Signatur oder eine Instanzmethode definieren, überprüft der Compiler diese nicht.

Die "richtige" Methode wird zur Laufzeit festgelegt. Der Compiler kann nicht wissen, ob dort zur Laufzeit eine gültige Methode vorhanden ist.

Das Schlüsselwort "dynamic" ist für dynamische Sprachen und Skriptsprachen definiert, in denen die Methode jederzeit definiert werden kann, auch zur Laufzeit. Verrücktes Zeug

Hier befindet sich ein Beispiel für die Instanz, das Ints, aber keine Zeichenfolgen verarbeitet.

class Program {
    static void Main(string[] args) {
        Example.Foo(1234);
        Example.Foo("1234");
    }
}
public class Example {
    int count;

    public static void Foo(dynamic x) {
        Bar(x);
    }

    public static void Bar(int a) {
        Console.WriteLine(a);
    }

    void Bar(dynamic x) {
        count++;
    }
}

Sie können eine Methode hinzufügen, um alle "falschen" Aufrufe zu verarbeiten, die nicht verarbeitet werden konnten

public class Example {
    int count;

    public static void Foo(dynamic x) {
        Bar(x);
    }

    public static void Bar<T>(T a) {
        Console.WriteLine("Error handling:" + a);
    }

    public static void Bar(int a) {
        Console.WriteLine(a);
    }

    void Bar(dynamic x) {
        count++;
    }
}
oberfreak
quelle
Sollte der aufrufende Code in Ihrem Beispiel nicht Example.Bar (...) anstelle von Example.Foo (...) sein? Ist Foo () in Ihrem Beispiel nicht irrelevant? Ich verstehe dein Beispiel nicht wirklich. Warum würde das Hinzufügen der statischen generischen Methode ein Problem verursachen? Könnten Sie Ihre Antwort so bearbeiten, dass sie diese Methode enthält, anstatt sie als Option anzugeben?
Mike Scott
Das von mir veröffentlichte Beispiel enthält jedoch nur eine einzige Instanzmethode und keine Überladungen. Beim Kompilieren wissen Sie also, dass es keine möglichen statischen Methoden gibt, die behoben werden könnten. Nur wenn Sie mindestens eine hinzufügen, ändert sich die Situation und der Code ist gültig.
Mike Scott
Dieses Beispiel enthält jedoch immer noch mehr als eine Bar () -Methode. Mein Beispiel hat nur eine Methode. Es gibt also keine Möglichkeit, eine statische Bar () -Methode aufzurufen. Der Aufruf kann zur Kompilierungszeit aufgelöst werden.
Mike Scott
@Mike kann sein! = Ist; Mit Dynamic ist dies nicht erforderlich
Marc Gravell
@MarcGravell, bitte klären?
Mike Scott