2015-05-22 13 views
6

mam przechowywania operację aktualizacji w następujący sposób:Unboxing typy nullable - obejście?

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

Na przykład:

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

Wtedy staram się zastosować tę aktualizację później (uproszczony):

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

Jednak myUpdate.Value to DateTime, a nie DateTime?. Dzieje się tak dlatego, że po rzuceniu wartości zerowej do object, staje się ona null lub powoduje skrzynie typu wartości.

Od (DateTime?)myUpdate.Value będzie działać, aby przywrócić go do prawidłowego typu, próbowałem symulować ten konstrukt kompilacji przy użyciu Convert.ChangeType. Jednak mówi, że odlewanie z DateTime do DateTime? nie jest możliwe.

Jakie podejście można zastosować, aby przywrócić obiekt do pierwotnego typu?

Czy istnieje sposób na przechowywanie typów zerowanych, regularnych struktur i zwykłych obiektów w jednym polu, i uzyskać dokładną rzecz do niego ponownie?

+3

Jaki jest twój problem? Czy 'prop.SetValue (item, myUpdate.Value)' throw any wyjątek dla ciebie? – PetSerAl

+1

Jak możesz mieć nadzieję, że metoda 'Convert.ChangeType' może być przydatna? Jest to nietypowa metoda, której deklarowanym typem powrotu jest 'object'. Więc ze względu na konwencje boksu dla 'Nullable <>' wspomniałeś o sobie, zwrócone pole z tej metody nie może być typu 'Nullable <>'. –

+0

PetSerAl - tak, ponieważ typy nie pasują. Przepraszam, może nie tak oczywiste, jak myślałem! –

Odpowiedz

1

Jeśli znasz typ swojego wyrażenia podczas tworzenia myUpdate, SetInfo() odpowiednio ustawi twoje dane, nawet jeśli boks/rozpakowanie.

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(); 
} 

To poprawnie wyprowadza wartość DateTime odpowiadającą temu, kiedy została utworzona bez wyjątku.

+0

Rzeczywiście masz rację, a mój oryginalny kod również był poprawny. SetValue rzucił "System.Reflection.TargetException: Object nie pasuje do typu celu.". Byłem, że NewValue był DateTime i właściwość DateTime? i domyśliłem się, że to było to. ..... w rzeczywistości mój "przedmiot" nie był prawidłowym typem: po prostu niewłaściwy obiekt został przekazany, mój błąd/błąd z innego miejsca. –

+0

@KierenJohnstone Ma doskonały sens. Zrobiłem to wiele razy. Cieszę się, że pomogło! –

+0

Stwierdziłem również, że mogę zbudować nowe wyrażenie: Expression.Assign (memberExpression, Expression.Constant (newValue)) i umieścić to w lambda, skompilować i uruchomić .. nie ma wtedy refleksji! Opublikuję jako osobną odpowiedź, ale zachowam ją jako poprawną –

1

Może możesz użyć ogólnej klasy?

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

Nie jestem pewien, czy można po prostu użyć zwykłego Func<MyType, TField> delegata zamiast drzewa ekspresji, do użytku.

+0

W rzeczywistości próbowałem tego, ale to, co mam, to zestaw tych, z TField nie wiadomo z góry. Musiałoby to wyprowadzić klasę bazową - a funkcja MemberExpression znów stałaby się wyrażeniem, chyba że Im brakuje luki? –

+0

@KierenJohnstone Tak, nie widzę na to rozwiązania. –

+1

Jeśli nie chcesz specjalnej konwersji boksowania dla 'Nullable ' (gdzie 'T' może być' DateTime'), możesz użyć np. referencja 'Tuple ' (jest to typ odniesienia) lub napisz własną 'struct Maybe ' która po prostu lubi 'Nullable ' ale ma zwykłą konwencję boksowania. –

0

udało mi się rozwiązać za pomocą wyrażeń i bez refleksji TOO:

 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); 

i voila, przedmiot jest modyfikowany :)