2012-05-25 12 views
6

aggiornamento: questo non è più un problema da C# 6, che ha introdotto l'operatore nameof per affrontare tali scenari (vedi MSDN).espressioni Lambda per refactoring-safe ArgumentException

Nota: Fare riferimento a "Getting names of local variables (and parameters) at run-time through lambda expressions" per una generalizzazione di questa domanda, nonché alcune risposte.

mi piace l'idea di usare espressioni lambda per creare implementazioni refactoring di sicurezza dell'interfaccia INotifyPropertyChanged, utilizzando codice simile a quello fornito da Eric De Carufel.

Sto sperimentando l'implementazione di qualcosa di simile per fornire il nome del parametro a un ArgumentException (o le sue classi derivate) in modo sicuro per i refactors.

ho definito il seguente metodo di utilità per l'esecuzione di null controlli:

public static void CheckNotNull<T>(Expression<Func<T>> parameterAccessExpression) 
{ 
    Func<T> parameterAccess = parameterAccessExpression.Compile(); 
    T parameterValue = parameterAccess(); 
    CheckNotNull(parameterValue, parameterAccessExpression); 
} 

public static void CheckNotNull<T>(T parameterValue, 
    Expression<Func<T>> parameterAccessExpression) 
{ 
    if (parameterValue == null) 
    { 
     Expression bodyExpression = parameterAccessExpression.Body; 
     MemberExpression memberExpression = bodyExpression as MemberExpression; 
     string parameterName = memberExpression.Member.Name; 
     throw new ArgumentNullException(parameterName); 
    } 
} 

convalida argomento può quindi essere effettuati in modo refactoring di sicurezza utilizzando la seguente sintassi:

CheckNotNull(() => arg);   // most concise 
CheckNotNull(arg,() => args);  // equivalent, but more efficient 

mia preoccupazione bugie nelle righe seguenti:

Expression bodyExpression = parameterAccessExpression.Body; 
MemberExpression memberExpression = bodyExpression as MemberExpression; 

A MemberExpression rappresenta "accesso a un campo o proprietà". È garantito che funzioni correttamente nel caso INotifyPropertyChanged, poiché l'espressione lambda sarebbe un accesso di proprietà.

Tuttavia, nel mio codice sopra, l'espressione lambda è semanticamente un parametro accesso, non un campo o accesso alla proprietà. L'unica ragione per cui il codice funziona è che il compilatore C# promuove qualsiasi variabile locale (e parametri) catturata in funzioni anonime per le variabili di istanza all'interno di una classe generata dal compilatore dietro le quinte. Questo è corroborato da Jon Skeet.

La mia domanda è: questo comportamento (di promozione dei parametri acquisiti in variabili di istanza) è documentato all'interno delle specifiche .NET, oppure si tratta solo di dettagli di implementazione che possono cambiare in implementazioni alternative o versioni future del framework? In particolare, potrebbero esserci ambienti in cui parameterAccessExpression.Body is MemberExpression restituisce false?

+1

Che dire di 'CheckNotNull (() => ((oggetto) 5) come stringa);'? – leppie

+1

Personalmente ho deciso che qualunque cosa 'Contract.Requires ' è abbastanza buono per me. Se una precondizione fallisce, è un bug. Non c'è bisogno di preoccuparsi per i dettagli. – CodesInChaos

+0

@CodeInChaos: che dire delle librerie che saranno rese disponibili per il consumo a terze parti? È previsto che i metodi pubblici eseguano il controllo degli argomenti e generino 'ArgumentException' con il nome del parametro corretto. – Douglas

risposta

0

Chiusure: come hai affermato, per l'accesso ai parametri, il compilatore C# (sì, in particolare il compilatore) crea una classe di chiusura che contiene campi di istanza per memorizzare il valore della variabile di parametro acquisita. Questo potrebbe cambiare con le versioni future del compilatore C#? Sicuro. Forse in una versione futura di C#, le classi di chiusura generate avranno variabili nominate casualmente dal momento che il nome non ha molta importanza in fase di runtime. Inoltre, il codice che hai potrebbe non funzionare per altri linguaggi .NET. Noterai che VB .NET genera alberi di espressione e classi di chiusura leggermente diversi da C# a volte ...

Non sono sicuro che la tua implementazione attuale funzionerà anche per le strutture (anche se potrei ricordare male ... la situazione che sto pensando di trattare con il pugilato potrebbe valere solo per Expression<Func<T, object>> (leggi, per favore provalo tu.)

Comunque ... tutto questo detto ... cambierà nelle versioni future di C#? Probabilmente no.Se lo fa, potresti cambiare la tua implementazione interna per gestirla probabilmente ...

Per quanto riguarda le prestazioni: sii molto attento qui. Hai già detto che sarebbe più efficiente passare due argomenti in modo da non dover compilare e valutare il lambda .... ma per essere chiari, stai parlando di un hit da 15 a 30ms ogni volta che compili e valutare.

+0

Accetto questa risposta poiché solleva un numero di punti validi (compresa la possibilità di rinominare le variabili promosse e l'incompatibilità con altri linguaggi .NET). Per riferimento, ho anche documentato la mia ricerca sulla [mancanza di standardizzazione di questo comportamento] (http://stackoverflow.com/a/11071271/1149773) sotto la mia altra domanda. – Douglas

Problemi correlati