2012-01-11 11 views
38
public void DoFoo<T>(T foo) where T : ISomeInterface<T> 
{ 
    //possible compare of value type with 'null'. 
    if (foo == null) throw new ArgumentNullException("foo"); 
} 

Io sto volutamente controllando solo su null perché non voglio limitare un ValueType da uguale al suo default(T). Il mio codice compila e funziona perfettamente in questo modo (si lamenta ReSharper, ma non CodeAnalysis). Anche se mi chiedo:Confronto di un generico rispetto a null che potrebbe essere un valore o un tipo di riferimento?

  • Esiste un altro standard di modo per gestire questa situazione?
  • C'è qualche possibilità che si verifichi un problema?
  • Che cosa succede veramente sotto il cofano quando faccio una chiamata e passo in un tipo di valore?

risposta

49

sto volutamente solo controllando contro null perché non voglio limitare un ValueType da essere uguale alla sua default(T)

Questa è una buona intuizione, ma non ti preoccupare, sei già coperto lì. Non è legale confrontare un T contro default(T) usando == in primo luogo; la risoluzione di sovraccarico non troverà un eccezionale operatore ==.

Ovviamente, è possibile eseguire il confronto con .Equals ma si rischia di bloccarsi se il ricevitore è nullo, che è esattamente ciò che si sta tentando di evitare.

Esiste un modo più standard per gestire questa situazione?

No. Il confronto con null è la cosa giusta da fare qui.

Come la specifica C# dice nella sezione 7.10.6: "La x == null costrutto è consentito anche se T potrebbe rappresentare un tipo di valore, e il risultato è semplicemente definito per essere falsa quando T è un tipo di valore."

C'è qualche possibilità che si verifichi un problema?

Sicuro. Solo perché la compilazione del codice non significa che abbia la semantica che intendi. Scrivi alcuni test.

Cosa succede veramente sotto il cofano quando faccio una chiamata e passo in un tipo di valore?

La domanda è ambigua.Lasciatemi riformulare in due domande:

Che cosa succede veramente sotto la cappa quando faccio una chiamata sul metodo generico con un argomento di tipo che è un tipo di valore non annullabile?

Il jitter compila il metodo alla prima chiamata con quella costruzione. Quando il jitter rileva il controllo Null, lo sostituisce con "false" perché sa che nessun tipo di valore non annullabile sarà mai uguale a null.

Cosa succede veramente sotto il cofano quando faccio una chiamata sul metodo generico con un argomento di tipo che è un tipo di riferimento, ma un argomento che è un tipo struct? Per esempio:

interface IFoo : ISomeInterface<IFoo> {} 
struct SFoo : IFoo { whatever } 
... 
DoFooInternal<IFoo>(new SFoo()); 

In tal caso il jitter non può elidere il controllo nullo e il sito chiamata non può evitare la boxe. L'istanza SFoo sarà racchiusa in una scatola, e il riferimento al box SFoo verrà controllato per vedere se è nullo.

+0

Mi confondeva ancora quando 'object.Equals (foo, default (T)))' dove 'foo' è un tipo di valore in box restituisce' false' (ad esempio 'foo' è un' DateTime' ma ottenuto tramite 'PropertyInfo '' s ​​'GetValue'). – mayu

+0

La verifica nulla viene ottimizzata per i tipi di valori nullable o la boxe si verifica in quel caso? Idealmente sarebbe convertito in chiamata HasValue, ma da quello che posso dire empiricamente un controllo == null gira molto lentamente per i tipi di valori nullable. – ChaseMedallion

+1

@ChaseMedallion: buona domanda, che avrei dovuto includere nella risposta originale. Credo che il jitter generi nuovamente il codice per i tipi di valori nullable, e quindi possa trasformare un assegno nullo in una chiamata a HasValue piuttosto che una casella seguita da un controllo nullo. Se effettivamente lo fa, non ricordo. Dovrebbe essere abbastanza facile da controllare però; basta scrivere un piccolo programma di test e guardare il codice x86 generato nel debugger. –

10

No, non ci sarà alcun problema, ma se si vuole l'avvertimento a scomparire, è possibile utilizzare il seguente:

public void DoFoo<T>(T foo) where T : ISomeInterface<T> 
{ 
    if (ReferenceEquals(foo, null)) throw new ArgumentNullException("foo"); 
} 

In alternativa si può fare qualcosa di simile:

// when calling this with an actual T parameter, you have to either specify the type 
// explicitly or cast the parameter to T?. 
public void DoFoo<T>(T? foo) where T : struct, ISomeInterface<T> 
{ 
    if (foo == null) 
    { 
     // throw... 
    } 

    DoFooInternal(foo.Value); 
} 

public void DoFoo<T>(T foo) where T : class, ISomeInterface<T> 
{ 
    if (foo == null) 
    { 
     // throw... 
    } 

    DoFooInternal(foo); 
} 

private void DoFooInternal<T>(T foo) where T : ISomeInterface<T> 
{ 
    // actual implementation 
} 
Problemi correlati