Dlaczego kompilator C # nie jest kodem błędu, w którym metoda statyczna wywołuje metodę wystąpienia?

110

Poniższy kod zawiera metodę statyczną Foo(), wywołując metodę instancji Bar():

public sealed class Example
{
    int count;

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

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

Kompiluje się bez błędów *, ale generuje wyjątek spinacza czasu wykonywania w czasie wykonywania. Zgodnie z oczekiwaniami usunięcie parametru dynamicznego z tych metod powoduje błąd kompilatora.

Dlaczego więc posiadanie parametru dynamicznego pozwala na kompilację kodu? ReSharper również nie pokazuje tego jako błędu.

Edytuj 1: * w programie Visual Studio 2008

Edycja 2: dodana, sealedponieważ jest możliwe, że podklasa może zawierać Bar(...)metodę statyczną . Nawet wersja zapieczętowana kompiluje się, gdy nie jest możliwe wywołanie metody innej niż metoda instancji w czasie wykonywania.

Mike Scott
źródło
8
+1 za bardzo dobre pytanie
cuongle
40
To jest pytanie Erica-Lipperta.
Olivier Jacot-Descombes
3
jestem prawie pewien, że Jon Skeet wiedziałby, co z tym zrobić;) @ OlivierJacot-Descombes
Thousand
2
@Olivier, Jon Skeet prawdopodobnie chciał, aby kod się skompilował, więc kompilator na to pozwala :-))
Mike Scott
5
To kolejny przykład, dlaczego nie powinieneś używać, dynamicchyba że naprawdę potrzebujesz.
Servy

Odpowiedzi:

71

AKTUALIZACJA: Poniższa odpowiedź została napisana w 2012 roku, przed wprowadzeniem C # 7.3 (maj 2018) . W Co nowego w C # 7,3 , w sekcji Ulepszeni kandydaci na przeciążenie , element 1, wyjaśniono, w jaki sposób zmieniły się reguły rozpoznawania przeciążenia, tak że niestatyczne przeciążenia są wcześnie odrzucane. Tak więc poniższa odpowiedź (i całe to pytanie) ma teraz głównie znaczenie historyczne!


(Przed C # 7.3 :)

Z jakiegoś powodu rozpoznawanie przeciążenia zawsze znajduje najlepsze dopasowanie przed sprawdzeniem statycznej i niestatycznej. Spróbuj tego kodu ze wszystkimi statycznymi typami:

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

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

To się nie skompiluje, ponieważ najlepszym przeciążeniem jest ten, który pobiera plik string. Ale hej, to jest metoda instancji, więc kompilator narzeka (zamiast pobierać drugie w kolejności przeciążenie).

Dodatek: Myślę więc, że wyjaśnienie dynamicprzykładu pierwotnego pytania jest takie, że aby zachować spójność, gdy typy są dynamiczne, najpierw znajdujemy również najlepsze przeciążenie (sprawdzanie tylko liczby parametrów i typów parametrów itp., A nie statyczne vs. nie -static), a dopiero potem sprawdź, czy jest statyczny. Ale to oznacza, że ​​kontrola statyczna musi czekać do czasu uruchomienia. Stąd obserwowane zachowanie.

Późne dodanie: Z tego posta na blogu Erica Lipperta można wywnioskować, dlaczego zdecydowali się robić rzeczy w tej zabawnej kolejności .

Jeppe Stig Nielsen
źródło
W pierwotnym pytaniu nie ma przeciążeń. Odpowiedzi pokazujące statyczne przeciążenie nie są istotne. Nie można odpowiadać „dobrze, jeśli to napisałeś ...”, ponieważ tego nie napisałem :-)
Mike Scott,
5
@MikeScott Po prostu próbuję cię przekonać, że rozwiązanie przeciążenia w C # zawsze wygląda tak: (1) Znajdź najlepsze dopasowanie bez uwzględnienia statycznego / niestatycznego. (2) Teraz wiemy, jakiego przeciążenia użyć, a następnie sprawdź , czy nie występuje statyczność. Z tego powodu, kiedy dynamiczostał wprowadzony w języku, myślę, że projektanci C # powiedzieli: „Nie będziemy rozważać (2) czasu kompilacji, gdy jest to dynamicwyrażenie”. Więc moim celem jest tutaj wymyślenie, dlaczego zdecydowali się nie sprawdzać statycznej i instancji do czasu wykonania. Powiedziałbym, że to sprawdzenie odbywa się w czasie wiązania .
Jeppe Stig Nielsen,
wystarczająco sprawiedliwe, ale nadal nie wyjaśnia, dlaczego w tym przypadku kompilator nie może rozpoznać wywołania metody instancji. Innymi słowy, sposób, w jaki kompilator rozwiązuje problem, jest uproszczony - nie rozpoznaje prostego przypadku, takiego jak mój przykład, w którym nie ma możliwości, że nie może rozwiązać wywołania. Ironia polega na tym, że mając pojedynczą metodę Bar () z parametrem dynamicznym, kompilator ignoruje tę pojedynczą metodę Bar ().
Mike Scott,
45
Napisałem tę część kompilatora C # i Jeppe ma rację. Prosimy o głosowanie. Rozpoznanie przeciążenia ma miejsce przed sprawdzeniem, czy dana metoda jest metodą statyczną, czy instancją, iw tym przypadku odraczamy rozpoznanie przeciążenia do czasu wykonania, a zatem również sprawdzenie statyczne / instancji do czasu wykonania. Ponadto kompilator dokłada wszelkich starań, aby statycznie znaleźć błędy dynamiczne, co jest absolutnie niekompletne.
Chris Burrows,
30

Foo ma parametr „x”, który jest dynamiczny, co oznacza, że ​​Bar (x) jest wyrażeniem dynamicznym.

W przypadku przykładu byłoby całkowicie możliwe zastosowanie metod takich jak:

static Bar(SomeType obj)

W takim przypadku poprawna metoda zostanie rozwiązana, więc instrukcja Bar (x) jest całkowicie poprawna. Fakt, że istnieje metoda instancji Bar (x) jest nieistotny i nawet nie brany pod uwagę: z definicji , ponieważ Bar (x) jest wyrażeniem dynamicznym, odłożyliśmy rozstrzygnięcie do czasu wykonania.

Marc Gravell
źródło
14
ale po wyjęciu metody Bar instancji nie jest ona już kompilowana.
Justin Harvey
1
@Justin ciekawy - ostrzeżenie? Albo błąd? Tak czy inaczej, może to być sprawdzanie poprawności tylko do grupy metod, pozostawiając pełne rozwiązanie przeciążenia w czasie wykonywania.
Marc Gravell
1
@Marc, ponieważ nie ma innej metody Bar (), nie odpowiadasz na pytanie. Czy możesz to wyjaśnić, biorąc pod uwagę, że istnieje tylko jedna metoda Bar () bez przeciążeń? Po co odkładać działanie środowiska uruchomieniowego, skoro nie ma możliwości wywołania innej metody? Czy jest? Uwaga: zmodyfikowałem kod, aby zapieczętować klasę, która nadal się kompiluje.
Mike Scott
1
@mike, aby dowiedzieć się, dlaczego odłożyć na czas działania: ponieważ to właśnie oznacza
Marc Gravell
2
@Mike niemożliwe nie jest celem; ważne jest, czy jest to wymagane . Cała sprawa z dynamiką polega na tym, że nie jest to zadanie kompilatora.
Marc Gravell
9

Wyrażenie „dynamiczne” będzie związane w czasie wykonywania, więc jeśli zdefiniujesz metodę statyczną z poprawnym podpisem lub metodą instancji, kompilator nie będzie tego sprawdzał.

„Właściwa” metoda zostanie określona w czasie wykonywania. Kompilator nie może wiedzieć, czy w czasie wykonywania jest tam prawidłowa metoda.

Słowo kluczowe „dynamic” jest zdefiniowane dla języków dynamicznych i skryptowych, w których Metodę można zdefiniować w dowolnym momencie, nawet w czasie wykonywania. Zwariowane rzeczy

Tutaj przykład, który obsługuje ints, ale bez łańcuchów, ze względu na metodę, znajduje się w instancji.

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++;
    }
}

Możesz dodać metodę obsługi wszystkich „złych” wywołań, których nie można obsłużyć

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
źródło
Czy kod wywołujący w twoim przykładzie nie powinien być Example.Bar (...) zamiast Example.Foo (...)? Czy w twoim przykładzie Foo () nie jest nieistotne? Naprawdę nie rozumiem twojego przykładu. Dlaczego dodanie statycznej metody ogólnej miałoby powodować problem? Czy możesz edytować swoją odpowiedź, aby uwzględnić tę metodę, zamiast podawać ją jako opcję?
Mike Scott
ale przykład, który opublikowałem, ma tylko jedną metodę wystąpienia i nie ma przeciążeń, więc w czasie kompilacji wiesz, że nie ma możliwych metod statycznych, które można by rozwiązać. Tylko jeśli dodasz co najmniej jeden, sytuacja się zmieni i kod będzie ważny.
Mike Scott
Ale ten przykład nadal ma więcej niż jedną metodę Bar (). Mój przykład ma tylko jedną metodę. Nie ma więc możliwości wywołania żadnej statycznej metody Bar (). Wywołanie można rozwiązać w czasie kompilacji.
Mike Scott
@Mike może być! = Jest; z dynamiką, nie jest to wymagane
Marc Gravell
@MarcGravell, proszę wyjaśnić?
Mike Scott,