2015-05-22 17 views
6

che sto memorizzare un'operazione di aggiornamento come così:Unboxing tipi nullable - soluzione alternativa?

class Update 
{ 
    public Expression MemberExpression { get; set; } 
    public Type FieldType { get; set; } 
    public object NewValue { get; set; } 
} 

Ad esempio:

var myUpdate = new Update 
{ 
    MemberExpression = (MyType o) => o.LastModified, 
    FieldType = typeof(DateTime?), 
    NewValue = (DateTime?)DateTime.Now 
} 

Poi sto cercando di applicare questo aggiornamento successivo (semplificato):

var lambda = myUpdate.MemberExpression as LambdaExpression; 
var memberExpr = lambda.Body as MemberExpression; 
var prop = memberExpr.Member as PropertyInfo; 
prop.SetValue(item, myUpdate.Value); 

Tuttavia, myUpdate.Value è un DateTime, non un DateTime?. Questo perché quando si esegue il cast di un valore nullo su un valore object, diventa null o il tipo di valore.

Poiché (DateTime?)myUpdate.Value avrebbe funzionato per riportarlo al tipo corretto, ho provato a simulare questo costrutto in fase di compilazione utilizzando Convert.ChangeType. Tuttavia, si dice che il casting da DateTime a DateTime? non è possibile.

Quale approccio posso utilizzare qui per riportare l'oggetto nel suo tipo originale?

C'è un modo per memorizzare tipi nullable, strutture normali e oggetti regolari in un singolo campo e ottenere di nuovo l'esatta cosa memorizzata?

+3

Qual è il tuo problema? 'Prop.SetValue (item, myUpdate.Value);' genera qualche eccezione per te? – PetSerAl

+1

Come puoi sperare che il metodo 'Convert.ChangeType' possa essere utile? È un metodo non generico il cui tipo di reso dichiarato è 'object'. Quindi, a causa delle convenzioni di boxing per 'Nullable <> 'che dici di te stesso, la casella restituita da quel metodo non può essere di tipo' Nullabile <> '. –

+0

PetSerAl - sì, poiché i tipi non corrispondono. Scusa, forse non così ovvio come pensavo! –

risposta

1

Se si conosce il tipo di espressione quando si crea myUpdate, SetInfo() imposterà correttamente i dati, anche se il boxing/unboxing.

public class Update 
{ 
    public Expression MemberExpression { get; set; } 
    public Type ClassType { get; set; } 
    public object NewValue { get; set; } 
} 

public class MyType 
{ 
    public DateTime? LastModified { get; set; } 
} 

void Main() 
{ 
    var myUpdate = new Update 
    { 
     MemberExpression = 
      (Expression<Func<MyType, DateTime?>>)((MyType o) => o.LastModified), 
     ClassType = typeof(MyType), 
     NewValue = DateTime.Now 
    }; 

    // At a later point where we do not want to instantiate via strong typing 
    var item = Activator.CreateInstance(myUpdate.ClassType); 

    var lambda = myUpdate.MemberExpression as LambdaExpression; 
    var memberExpr = lambda.Body as MemberExpression; 
    var prop = memberExpr.Member as PropertyInfo; 

    prop.SetValue(item, myUpdate.NewValue); 

    item.Dump(); 
} 

In questo modo viene emesso correttamente un valore DateTime corrispondente a quando è stato creato senza eccezioni.

+0

In effetti tu hai ragione e anche il mio codice originale era corretto. SetValue lanciava "System.Reflection.TargetException: l'oggetto non corrisponde al tipo di destinazione.". Ero che NewValue era DateTime e la proprietà era DateTime? e ho indovinato che era quello. ..... infatti, il mio 'oggetto' non era del tipo corretto: semplicemente l'oggetto sbagliato passato, il mio errore/errore da altrove. –

+0

@KierenJohnstone Ha perfettamente senso. L'ho fatto molte volte. Sono contento che ci abbia aiutato! –

+0

Ho anche scoperto che posso creare una nuova espressione: Expression.Assign (memberExpression, Expression.Constant (newValue)) e inserirla in una lambda, compilarla ed eseguirla .. nessun riflesso allora! Inserirò una risposta separata, ma mantieni quella corretta come –

1

Forse è possibile utilizzare una classe generica?

class Update<TField> 
{ 
    public Expression<Func<MyType, TField>> MemberExpression { get; set; } 
    public TField NewValue { get; set; } 
} 

Non sono sicuro se si può semplicemente utilizzare una pianura Func<MyType, TField> delegato al posto del albero di espressione, per l'uso.

+0

In realtà ho provato questo, ma quello che ho è un array di questi, con il TField non noto in anticipo. Ciò dovrebbe derivare una classe base e MemberExpression diventerebbe di nuovo un'espressione, a meno che non manchi un trucco? –

+0

@KierenJohnstone Sì, non vedo alcuna soluzione per questo. –

+1

Se non si desidera la conversione di boxing speciale per 'Nullable ' (dove 'T' potrebbe essere' DateTime'), è possibile utilizzare ad es. un riferimento '' Tuple '(questo è un tipo di riferimento), o scrivi il tuo' struct Forse 'che fa proprio come' Nullable 'ma ha una convenzione di boxe ordinaria. –

0

sono stato in grado di risolvere utilizzando espressioni e nessuna riflessione troppo:

 var lambda = update.MemberExpression as LambdaExpression; 
     var memberExpr = lambda.Body as MemberExpression; 
     if (memberExpr == null) 
     { 
      throw new NotSupportedException("Field expression must be a member expression"); 
     } 

     // do a cast - this ensures nullable types work, for instance 
     var cast = Expression.Convert(Expression.Constant(update.Value), update.FieldType); 

     // assign the target member with the cast value 
     var assignment = Expression.Assign(memberExpr, cast); 

     // build a new lambda, no return type, which does the assignment 
     var newLambda = Expression.Lambda(typeof(Action<T>), assignment, lambda.Parameters[0]); 

     // compile to something we can invoke, and invoke 
     var compiled = (Action<T>)newLambda.Compile(); 

     compiled(item); 

e voilà, l'elemento viene modificato :)

Problemi correlati