Azeeb
Azeeb

Reputation: 91

Expression tree to create lambda for generating a c# dictionary

I have a stream of thousands of data which I need to transform and add to a list. The transformation happens through the reflection similar to following

_myObservable.Subscribe(d => {
    PropertyInfo[] props = d.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);
    var propValDict = props.ToDictionary(prop => prop.Name, prop => prop.GetValue(d, null));
    myList.Add(propValDict);
});

// Datatype of d is determined during runtime and there are only 8 possibilities of the type

But this approach is slowing down the performance and I expect the use of reflection might be the reason. I am thinking of improving the performance by some other means.

Suggestions seem to point at use of Expression trees, create compiled lambda (Func<object,Dictionary<string, object>>) and store it in a lookup dictionary before hand.

//Foreach possibleType in PossibleTypes, Do below

PropertyInfo[] props = possibleType.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);

var rootParam = Expression.Parameter(typeof(object), "d");
var param = Expression.Parameter(typeof(PropertyInfo), "prop");
var propertyFirst = Expression.Property(param, "Name");

var param2 = Expression.Parameter(typeof(PropertyInfo), "prop");
var callMethod = Expression.Call(param2, typeof(PropertyInfo).GetMethod(nameof(PropertyInfo.GetValue), new Type[] { typeof(object) }), rootParam);

var pro = Expression.Parameter(typeof(Array), "props");

var toDict = Expression.Invoke(pro, propertyFirst, callMethod);
var lambda = Expression.Lambda<Func<object, Dictionary<string, object>>>(toDict, rootParam);
var compiled = lambda.Compile();

I am having trouble to invoke ToDictionary of Enumerable class There is something which I am missing with this approach or Will this really improve performance.

Please help...

Upvotes: 2

Views: 248

Answers (1)

canton7
canton7

Reputation: 42320

When thinking with expressions, you always need to figure out what the equivalent C# code would look like. In this case, the equivalent C# code wouldn't be looping over a collection of PropertyInfo, instead it would probably look like:

public static Func<object, Dictionary<string, object>> CreateConvertToPropertyDict<T>()
{
    return input =>
    {
        var d = (T)input;
        return new Dictionary<string, object>())
        {
            { "Foo", d.Foo },
            { "Bar", d.Bar },
        };
    };
}
myList.Add(propValDict);

Move sidewards in the land of expressions, and you end up with something like:

public static Func<object, Dictionary<string, object>> CreatePropertyDict(Type type)
{
    // Consider caching these in a static field, since they're constant
    var dictType = typeof(Dictionary<string, object>);
    var dictCtor = dictType.GetConstructor(new[] { typeof(int) });
    var dictAddMethod = dictType.GetMethod("Add");

    var properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public);
    
    var blockExpressions = new List<Expression>();
    
    // 'object input' is our input parameter
    var inputParameter = Expression.Parameter(typeof(object), "input");
    // MyType d;
    var dVariable = Expression.Variable(type, "d");
    // d = (MyType)inputObject;
    blockExpressions.Add(Expression.Assign(dVariable, Expression.Convert(inputParameter, type)));
    // Dictionary<string, object> dict;
    var dictVariable = Expression.Variable(dictType, "dict");
    // dict = new Dictionary<string, object>(3) (or however many properties there are)
    blockExpressions.Add(Expression.Assign(dictVariable, Expression.New(dictCtor, Expression.Constant(properties.Length))));
    
    foreach (var property in properties)
    {
        var propertyAccess = Expression.Property(dVariable, property);
        // dict.Add("Foo", (object)d.Foo)
        blockExpressions.Add(Expression.Call(
            dictVariable,
            dictAddMethod,
            Expression.Constant(property.Name),
            Expression.Convert(propertyAccess, typeof(object))));
    };
    
    // The final statement in a block is the return value
    blockExpressions.Add(dictVariable);
    
    var block = Expression.Block(new[] { dVariable, dictVariable }, blockExpressions);
    return Expression.Lambda<Func<object, Dictionary<string, object>>>(block, inputParameter).Compile();
}

With the simple test case:

public static void Main()
{
    var test = new Test() { Foo = "woop", Bar = 3 };
    var expr = CreatePropertyDict(typeof(Test));
    expr(test).Dump();
}

See it on dotnetfiddle.

There are several more advanced usages of Expression here, and I'm not going to go into the detail of each one. Look at the docs, and have a play around with the sorts of expressions that the C# compiler generates for different bits of C# code.

Upvotes: 3

Related Questions