2014-10-10 21 views
9

Ho questo codice (minimizzato per chiarezza):Perché il compilatore C# non può dedurre i parametri di tipo per i valori di ritorno?

interface IEither<out TL, out TR> { 
} 

class Left<TL, TR> : IEither<TL, TR> { 
    public Left(TL value) { } 
} 

static class Either { 
    public static IEither<TL, TR> Left<TL, TR> (this TL left) { 
     return new Left<TL, TR> (left); 
    } 
} 

Perché non posso dire:

static class Foo 
{ 
    public static IEither<string, int> Bar() 
    { 
     //return "Hello".Left(); // Doesn't compile 
     return "Hello".Left<string, int>(); // Compiles 
    } 
} 

ottengo un errore che indica che 'string' does not contain a definition for 'Left' and no extension method 'Left' accepting a first argument of type 'string' could be found (are you missing a using directive or an assembly reference?) (CS1061).

+0

Questa è una domanda molto buona, soprattutto considerando che è possibile restituire lambda allo stesso modo e l'inferenza di tipo funzionerà correttamente. Ma temo che possiamo solo indovinare. –

+1

Non sono sicuro cosa siano 'Foo' e' Bar'. Puoi pubblicare un campione riproducibile completo? –

+0

@SriramSakthivel ha aggiunto MWE – miniBill

risposta

5

Si consiglia di dare un'occhiata alla sezione 7.5.2 nella specifica C#.

7.5.2 inferenza di tipo

Quando un metodo generico viene chiamato senza specificare argomenti di tipo, un processo inferenza tenta di dedurre argomenti di tipo per la chiamata. La presenza di tipo di inferenza consente di utilizzare una sintassi più comoda per utilizzata per chiamare un metodo generico e consente al programmatore di evitare specificando informazioni di tipo ridondanti.

[...]

inferenza di tipo avviene come parte dell'elaborazione tempo legame di una chiamata di metodo (§7.6.5.1) e avviene prima della fase di risoluzione sovraccarico del invocazione [...]

Ie la risoluzione avviene prima di qualsiasi tipo di risoluzione di sovraccarico è fatto, il problema è che inferire i tipi il modo in cui hai detto che non è nemmeno provato, e non è francamente sempre possibile sia.

La risoluzione dei tipi per gli argomenti per i tipi generici viene eseguita solo con gli argomenti nell'invocazione! Nel tuo esempio solo un string! Non può dedurre int dagli argomenti, solo il contesto di chiamata che non è risolto al momento della risoluzione di tipo generico.

+0

Quindi è semplicemente una questione di "non lo stiamo facendo in un modo che permetta questo tipo di inferenza"? – miniBill

+0

Esattamente. Direi che non è intenzionale, ma solo un effetto collaterale di come viene implementata la risoluzione del tipo in merito alla risoluzione del sovraccarico. – flindeberg

1

Disclaimer: questa risposta richiede indovinare.

Le informazioni su TR sta nel fatto che è usato come espressione figlio di return, che, a sua volta, potrebbe essere abbinato con IEither<Foo, Bar>, per produrre le informazioni che TR è Bar.

Ma c'è un problema. Quando il compilatore ha un albero di sintassi astratto, è più semplice iniziare dalla radice e inferire i tipi di espressione, risolvere sovraccarichi, ecc spostandosi progressivamente verso le foglie dell'albero. Questa è la cosa più facile da fare e la cosa che viene fatta più spesso.

tuo scenario richiede il compilatore di lavorare completamente all'indietro - dalla chiamata di metodo per la chiamata al costruttore per l'espressione del bambino di return e poi di consultare la dichiarazione del metodo corrente (e scoprire che la combinazione corretta è <string, int>, perché qualsiasi altro non può eventualmente compilare). A prima vista, questo è difficile da raggiungere.

Ma v'è una certa precedenza per questo in C#: è possibile creare una funzione (di ordine superiore) che restituisce semplicemente un lambda e l'inferenza di tipo volontà lavoro basato sulla dichiarazione. Questo tipo di cose funziona anche con lambdas quando si dichiara una variabile locale (non è possibile assegnarli a var e chiedere al compilatore di dedurre i tipi di parametri dall'utilizzo successivo nell'ambito).

Quindi, perché non l'hanno implementato per il caso che stai descrivendo, considerando che lo hanno fatto con lambda?

  1. Essi avevano di farlo con lambda: il punto di espressioni lambda è che non dovrebbe essere simile a un grosso problema - che dovrebbe essere il più facile scrivere il più possibile (per esprimere rapidamente una criterio di filtraggio, criterio di selezione, ecc.). Questo ha un valore.
  2. Anche se questo genere di cose funziona con lambda, è tutt'altro che perfetto - in effetti, può comportarsi in modi molto strani di volta in volta. Quindi può essere visto dai designer come un male necessario che non dovrebbe essere esteso al resto della lingua.
  3. Il tuo caso è più raro e la soluzione alternativa è semplice.
  4. Può essere semplicemente il caso di "nessuno lo ha implementato ma".
6
return "Hello".Left<string, int>(); // Compiles 

Nessuna sorpresa. Hai dichiarato esplicitamente i parametri del tipo e il compilatore è soddisfatto.

return "Hello".Left(); // Doesn't compile 

Nessuna sorpresa neanche, compilatore non ha modo di scoprire che cosa è TR in questo caso. TL può essere dedotto perché TL viene passato come parametro left ma non è possibile impostare TR.

Il compilatore C# non fa presupposizioni in quello che intendevi, Se l'intento non è chiaro, verrà generato un errore del compilatore. Se il compilatore pensa che potresti fare qualcosa di sbagliato, viene visualizzato un avviso del compilatore.

+0

Il mio punto è che l'unico valore possibile per 'TR' è int, e mi piacerebbe sapere perché il compilatore non può capirlo da solo – miniBill

+4

L'unico valore possibile di 'TR' * per il valore di ritorno è accettabile * è' int'. 'TR' non ha questo vincolo. – Alex

+1

+1 'Il compilatore C# non fa supposizioni' – Maarten

Problemi correlati