Question

How do you create a lambda expression for a function that can handle unknown types? Sorry, I know the question is vague and I had a hard time forming it. I can only hope you have a minute and read through my story, which should clear things up a bit.

My goal is to deserialize an array of string values into an object using a predefined data contract. The members of the data contract have a position number. The deserializer's simple job is to map a value to a data member (after doing the appropriate type conversion), and build the object.

The problem is that the deserialization performance sucks! After running VS Profiler, I found that PropertyInfo.SetValue(), which is what is used to populate the members of an object, is taking the most time. My program has to deserialize thousands of object at any given time. A data contract usually has 100 members. So we are talking 100,000 calls to SetValue() for each 1000 objects, and it's dragging. Here's a sample of the call to SetValue:

// for each data contract type
// go through each property and set the value
foreach(PropertyInfo pi in pis)
{
    object data = convertStringToMemberType(pi, attributeArray, valueStringArray);
    pi.SetValue(objectToBuild, data, null);
}

Then I found this page from Unknown Recipes, which has a promising solution to this performance problem. It looks like I need to use a compiled lambda expression to replace SetValue, but I'm running into a problem with casting. Following the example form the link above, I now have a replacement for SetValue(). The replacements are Action delegates, which are compiled lambda expressions.

First, I extended the PropertyInfo class.

public static class PropertyInfoExtensions
{

    public static Action<object, object> GetValueSetter(this PropertyInfo propertyInfo)
    {
        var instance = Expression.Parameter(propertyInfo.DeclaringType, "i");
        var argument = Expression.Parameter(typeof(object), "a");
        var setterCall = Expression.Call(
            instance,
            propertyInfo.GetSetMethod(),
            Expression.Convert(argument, propertyInfo.PropertyType));
        return (Action<object, object>)Expression.Lambda(setterCall, instance, argument).Compile();
    }
}

Then I built a Dictionary<PropertyInfo, Action<object, object> object, which ties each propertyInfo object to its corresponding Action delegate. This way I can "cache" the compiled lambda and reuse it in a batch of deserialization. And this is how I call it now:

foreach(PropertyInfo pi in pis)
{
    object data = convertStringToMemberType(pi, attributeArray, valueStringArray);
    var setValueDelegate = _actionDelegateDict[pi];
    setValueDelegate(objectToBuild, data);
}

However, I am getting the following exception:

Unable to cast object of type 'System.Action`2[Test.DataContract1,System.Object]' to type 'System.Action`2[System.Object,System.Object]'.

Here DataContract1 is the type of the object I'm trying to build. It is known only at run time, which is unlike the scenario in the example from Unknown Recipes, where the type is known at compile time. How would you go about making this lambda expression work?

Thank you so very much for your time!

Was it helpful?

Solution

Sounds a lot like what I did with my FastReflection library. You are pretty much there, you would just change the instance parameter to object type and then do a cast on that expression to the actual type.

I think your code you have right now would work if it were changed to this.

public static class PropertyInfoExtensions
{
    public static Action<object, object> GetValueSetter(this PropertyInfo propertyInfo)
    {
        var instance = Expression.Parameter(typeof(object), "i");
        var argument = Expression.Parameter(typeof(object), "a");
        var setterCall = Expression.Call(
            Expression.Convert(instance, propertyInfo.DeclaringType),
            propertyInfo.GetSetMethod(),
            Expression.Convert(argument, propertyInfo.PropertyType));
        return Expression.Lambda<Action<object,object>>(setterCall, instance, argument).Compile();
    }
}

OTHER TIPS

Imagine you were creating this lambda directly, not using Expressions. How would that look like? You want to create Action<object, object>:

Action<object, object> action = (object i, object a) => i.Property = a;

But this wouldn't work, you need to cast both i and a. So:

Action<object, object> action =
    (object i, object a) => ((DataContract)i).Property = (PropertyType)a;

In your code, you're casting a, but you need to cast i too:

public static Action<object, object> GetValueSetter(this PropertyInfo propertyInfo)
{
    var instance = Expression.Parameter(typeof(object), "i");
    var argument = Expression.Parameter(typeof(object), "a");
    var setterCall = Expression.Call(
        Expression.Convert(instance, propertyInfo.DeclaringType),
        propertyInfo.GetSetMethod(),
        Expression.Convert(argument, propertyInfo.PropertyType));
    return (Action<object, object>)Expression.Lambda(setterCall, instance, argument).Compile();
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top