Perché il compilatore C # non genera errori nel codice in cui un metodo statico chiama un metodo di istanza?


110

Il seguente codice ha un metodo statico, Foo(), chiamando un metodo di istanza, Bar():

public sealed class Example
{
    int count;

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

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

Si compila senza errori * ma genera un'eccezione del binder di runtime in fase di runtime. La rimozione del parametro dinamico a questi metodi causa un errore del compilatore, come previsto.

Allora perché avere un parametro dinamico consente di compilare il codice? Anche ReSharper non lo mostra come un errore.

Modifica 1: * in Visual Studio 2008

Modifica 2: aggiunta sealedpoiché è possibile che una sottoclasse possa contenere un Bar(...)metodo statico . Anche la versione sigillata viene compilata quando non è possibile che un metodo diverso dal metodo dell'istanza possa essere chiamato in fase di esecuzione.


8
+1 per un'ottima domanda
cuongle

40
Questa è una domanda di Eric-Lippert.
Olivier Jacot-Descombes

3
sono abbastanza sicuro che anche Jon Skeet saprebbe cosa fare con questo;) @ OlivierJacot-Descombes
Mille

2
@Olivier, Jon Skeet probabilmente voleva che il codice si compilasse, quindi il compilatore lo permette :-))
Mike Scott

5
Questo è un altro esempio del motivo per cui non dovresti usare a dynamicmeno che non sia davvero necessario.
Servizio

Risposte:


71

AGGIORNAMENTO: la risposta seguente è stata scritta nel 2012, prima dell'introduzione di C # 7.3 (maggio 2018) . In Novità di C # 7.3 , la sezione Candidati all'overload migliorati , elemento 1, viene spiegato come sono cambiate le regole di risoluzione dell'overload in modo che gli overload non statici vengano scartati in anticipo. Quindi la risposta di seguito (e l'intera domanda) ha per lo più solo interesse storico ormai!


(Pre C # 7.3 :)

Per qualche ragione, la risoluzione dell'overload trova sempre la migliore corrispondenza prima di controllare statico e non statico. Prova questo codice con tutti i tipi statici:

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

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

Questo non verrà compilato perché il miglior sovraccarico è quello che prende un file string. Ma hey, questo è un metodo di istanza, quindi il compilatore si lamenta (invece di prendere il secondo miglior sovraccarico).

Aggiunta: quindi penso che la spiegazione dynamicdell'esempio della domanda originale sia che, per essere coerenti, quando i tipi sono dinamici troviamo prima anche il miglior sovraccarico (controllando solo il numero di parametro e i tipi di parametro ecc., Non statico vs. non -static), e solo allora verifica la presenza di static. Ma ciò significa che il controllo statico deve attendere fino al runtime. Da qui il comportamento osservato.

Aggiunta tardiva: alcune informazioni sul motivo per cui hanno scelto di fare le cose con questo divertente ordine possono essere dedotte da questo post sul blog di Eric Lippert .


Non ci sono sovraccarichi nella domanda originale. Le risposte che mostrano un sovraccarico statico non sono rilevanti. Non è valido rispondere "beh se hai scritto questo ..." dato che non l'ho scritto io :-)
Mike Scott

5
@ MikeScott Cerco solo di convincerti che la risoluzione del sovraccarico in C # funziona sempre così: (1) Trova la migliore corrispondenza ignorando statico / non statico. (2) Ora sappiamo quale sovraccarico usare, quindi controlla statico. Per questo motivo, quando è dynamicstato introdotto nel linguaggio, penso che i progettisti di C # abbiano detto: "Non considereremo (2) il tempo di compilazione quando è dynamicun'espressione." Quindi il mio scopo qui è di trovare un'idea del motivo per cui hanno scelto di non verificare la staticità rispetto all'istanza fino al runtime. Direi che questo controllo avviene al momento del binding .
Jeppe Stig Nielsen

abbastanza giusto ma ancora non spiega perché in questo caso il compilatore non può risolvere la chiamata al metodo dell'istanza. In altre parole, il modo in cui il compilatore esegue la risoluzione è semplicistico: non riconosce il caso semplice come il mio esempio in cui non c'è possibilità che non possa risolvere la chiamata. L'ironia è: avendo un singolo metodo Bar () con un parametro dinamico, il compilatore ignora quel singolo metodo Bar ().
Mike Scott

45
Ho scritto questa parte del compilatore C # e Jeppe ha ragione. Per favore vota questo. La risoluzione del sovraccarico avviene prima di verificare se un determinato metodo è un metodo statico o di istanza, e in questo caso rimandiamo la risoluzione del sovraccarico al runtime e quindi anche il controllo statico / istanza fino al runtime. Inoltre, il compilatore fa il "massimo sforzo" per trovare staticamente gli errori dinamici che non è assolutamente completo.
Chris Burrows

30

Foo ha un parametro "x" che è dinamico, il che significa che Bar (x) è un'espressione dinamica.

Sarebbe perfettamente possibile per Example avere metodi come:

static Bar(SomeType obj)

In tal caso il metodo corretto verrebbe risolto, quindi l'istruzione Bar (x) è perfettamente valida. Il fatto che esista un metodo di istanza Bar (x) è irrilevante e nemmeno considerato: per definizione , poiché Bar (x) è un'espressione dinamica, abbiamo rimandato la risoluzione al runtime.


14
ma quando si elimina il metodo Bar dell'istanza, non viene più compilato.
Justin Harvey,

1
@Justin interessante - un avvertimento? O un errore? In ogni caso, potrebbe essere convalidato solo fino al gruppo di metodi, lasciando la piena risoluzione dell'overload al runtime.
Marc Gravell

1
@ Marc, poiché non esiste un altro metodo Bar (), non stai rispondendo alla domanda. Puoi spiegarlo dato che esiste un solo metodo Bar () senza sovraccarichi? Perché rimandare al runtime quando non è possibile chiamare nessun altro metodo? Oppure c'è? Nota: ho modificato il codice per sigillare la classe, che viene comunque compilata.
Mike Scott

1
@mike per quanto riguarda il motivo per rimandare al runtime: perché questo è ciò che significa dinamico
Marc Gravell

2
@ Mike impossibile non è il punto; ciò che è importante è se è necessario . L'intero punto con la dinamica è che non è compito del compilatore.
Marc Gravell

9

L'espressione "dinamica" verrà associata durante il runtime, quindi se si definisce un metodo statico con la firma corretta o un metodo di istanza, il compilatore non lo controllerà.

Il metodo "corretto" verrà determinato durante il runtime. Il compilatore non può sapere se esiste un metodo valido durante il runtime.

La parola chiave "dinamica" è definita per linguaggi dinamici e script, dove il Metodo può essere definito in qualsiasi momento, anche durante il runtime. Roba da matti

Qui un esempio che gestisce gli interi ma non le stringhe, poiché il metodo è sull'istanza.

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

È possibile aggiungere un metodo per gestire tutte le chiamate "sbagliate", che non è stato possibile gestire

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

Il codice chiamante nel tuo esempio non dovrebbe essere Example.Bar (...) invece di Example.Foo (...)? Foo () non è irrilevante nel tuo esempio? Non capisco davvero il tuo esempio. Perché l'aggiunta del metodo generico statico causa un problema? Potresti modificare la tua risposta per includere quel metodo invece di darlo come opzione?
Mike Scott

ma l'esempio che ho pubblicato ha un solo metodo di istanza e nessun sovraccarico, quindi in fase di compilazione sai che non ci sono metodi statici possibili che potrebbero essere risolti. Solo se ne aggiungi almeno uno la situazione cambia e il codice è valido.
Mike Scott

Ma questo esempio ha ancora più di un metodo Bar (). Il mio esempio ha un solo metodo. Quindi non c'è la possibilità di chiamare alcun metodo statico Bar (). La chiamata può essere risolta in fase di compilazione.
Mike Scott

@Mike può essere! = È; con dinamico, non è necessario farlo
Marc Gravell

@ MarcGravell, per favore chiarisci?
Mike Scott
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.