Recently, I came across some troubles about boxing using Expression Trees when I was developing my homemade SQLite ORM. I am still coding again C# 3.5.
To make a long story short, I'm gonna use this simple class definition:
[Table]
public class Michelle
{
[Column(true), PrimaryKey]
public UInt32 A { get; set; }
[Column]
public String B { get; set; }
}
So this from that POCO class definition I instantiated a new object and my ORM engine converted that object into a record and it's properly inserted. Now the trick is that when I get back the value from SQLite I got an Int64
.
I thought that my delegate setter was OK because it's an Action<Object, Object>
but I still got that InvalidCastException
. Seems that the Object
(parameter, an Int64
) is attempted to be cast into a UInt32
. Unfortunately it does not work. I tried to add some Expression.Constant
and Expression.TypeAs
(that one does not really help for value typed objects).
So I am wondering what sort of things are wrong in my setter generation.
Here below is my setter generation method:
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];
}
So basically:
// 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);
I should probably add another Expression.Convert
but I do not really know how it would help to make it work. Since there is already one supposed to (attempt to) convert from any Object
to the property type (here in my example the UInt32
type).
Any idea to fix it up?
For this answer, assume the following is defined:
object myColValueFromTheDatabase = (object)64L;
Expression.Convert
determines statically how the conversion is to be performed. Just like C# does. If you write (uint)myColValueFromTheDatabase
this will not succeed at runtime because unboxing just does not work that way. Expression.Convert
does a simple unboxing attempt as well. That's why it fails.
You would need to do either of the following:
(uint)(long)myColValueFromTheDatabase
Convert.ToUInt32(myColValueFromTheDatabase)
In case (1) you need to unbox to the exact-match type first, then change the bits. Case (2) resolves this using some helper methods. Case (1) is faster.
To do this with the expression API, insert another Expression.Convert
.
This should get you started:
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);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With