Recentemente, mi sono imbattuto in alcuni problemi relativi al pugilato usando Expression Trees mentre stavo sviluppando il mio ORM SQLite fatto in casa. Sto ancora scrivendo di nuovo C # 3.5.
Per farla breve, userò questa semplice definizione di classe:
[Table]
public class Michelle
{
[Column(true), PrimaryKey]
public UInt32 A { get; set; }
[Column]
public String B { get; set; }
}
Quindi, da quella definizione della classe POCO, ho istanziato un nuovo oggetto e il mio motore ORM ha convertito quell'oggetto in un record ed è stato inserito correttamente. Ora il trucco è che quando torno al valore di SQLite ho un Int64
.
Ho pensato che il mio setter delegato fosse OK perché è un Action<Object, Object>
ma ho ancora InvalidCastException
. Sembra che l' Object
(parametro, un Int64
) sia stato lanciato in un UInt32
. Sfortunatamente non funziona. Ho provato ad aggiungere Expression.Constant
ed Expression.TypeAs
(che non aiuta molto per gli oggetti con valore typed).
Quindi mi sto chiedendo che tipo di cose sono sbagliate nella mia generazione setter.
Qui di seguito è il mio metodo di generazione setter:
public static Action<Object, Object> GenerateSetter(PropertyInfo propertyInfo)
{
if (!FactoryFastProperties.CacheSetters.ContainsKey(propertyInfo))
{
MethodInfo methodInfoSetter = propertyInfo.GetSetMethod();
ParameterExpression parameterExpressionInstance = Expression.Parameter(FactoryFastProperties.TypeObject, "Instance");
ParameterExpression parameterExpressionValue = Expression.Parameter(FactoryFastProperties.TypeObject, "Value");
UnaryExpression unaryExpressionInstance = Expression.Convert(parameterExpressionInstance, propertyInfo.DeclaringType);
UnaryExpression unaryExpressionValue = Expression.Convert(parameterExpressionValue, propertyInfo.PropertyType);
MethodCallExpression methodCallExpression = Expression.Call(unaryExpressionInstance, methodInfoSetter, unaryExpressionValue);
Expression<Action<Object, Object>> expressionActionObjectObject = Expression.Lambda<Action<Object, Object>>(methodCallExpression, new ParameterExpression[] { parameterExpressionInstance, parameterExpressionValue });
FactoryFastProperties.CacheSetters.Add(propertyInfo, expressionActionObjectObject.Compile());
}
return FactoryFastProperties.CacheSetters[propertyInfo];
}
Quindi in poche parole:
// Considering setter as something returned by the generator described above
// So:
// That one works!
setter(instance, 32u);
// This one... hm not really =/
setter(instance, 64);
Probabilmente dovrei aggiungere un altro Expression.Convert
ma non so davvero come sarebbe utile farlo funzionare. Poiché esiste già un tentativo (tentativo di) di convertire da qualsiasi Object
al tipo di proprietà (qui nel mio esempio il tipo UInt32
).
Qualche idea per sistemarlo?
Per questa risposta, si assuma quanto segue:
object myColValueFromTheDatabase = (object)64L;
Expression.Convert
determina staticamente come deve essere eseguita la conversione. Proprio come C #. Se scrivi (uint)myColValueFromTheDatabase
questo non avrà successo in fase di runtime perché l'unboxing non funziona in questo modo. Expression.Convert
esegue anche un semplice tentativo di annullamento. Ecco perché fallisce.
Dovresti effettuare una delle seguenti operazioni:
(uint)(long)myColValueFromTheDatabase
Convert.ToUInt32(myColValueFromTheDatabase)
Nel caso (1) devi prima rimuovere unbox dal tipo esatto, quindi cambiare i bit. Case (2) risolve questo usando alcuni metodi di supporto. Il caso (1) è più veloce.
Per fare ciò con l'espressione API, inserisci un altro Expression.Convert
.
Questo dovrebbe farti cominciare:
public static T LogValue<T>(T val)
{
Console.WriteLine(val.GetType().Name + ": " + val);
return val;
}
static void Main(string[] args)
{
Expression myColValueFromTheDatabase = Expression.Convert(Expression.Constant(1234L), typeof(object));
myColValueFromTheDatabase = Expression.Call(typeof(Program), "LogValue", new[] { myColValueFromTheDatabase.Type }, myColValueFromTheDatabase); //log
Expression unboxed = Expression.Convert(myColValueFromTheDatabase, typeof(long));
Expression converted = Expression.Convert(unboxed, typeof(uint));
var result = Expression.Lambda<Func<uint>>(converted).Compile()();
Console.WriteLine(result);
}