سؤال

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:

  1. (uint)(long)myColValueFromTheDatabase
  2. 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);
    }
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top