Search code examples
c#reflectionexpressionexpression-trees

Expression tree create dictionary with property values for class


Essentially I'm trying to do this using expression trees

var properties = new Dictionary<string, object>();

foreach (var propInfo in objType.GetTypeInfo().GetProperties(BindingFlags.Public))
{
    var name = propInfo.Name;
    var value = propInfo.GetValue(objInstance);

    properties.Add(name, value);
}

return properties;

I.e. create a dictionary of name and value pairs where name is the name of a property for objType and value is the value of the property for the instance objInstance of objType

Now converting this to an expression should compile to a delegate that simply does

Func<T, Dictionary<string, object>> func = i =>
{
    var properties = new Dictionary<string, object>();

    properties.Add("Prop1", (object)i.Prop1);
    properties.Add("Prop2", (object)i.Prop2);
    properties.Add("Prop3", (object)i.Prop3);
    // depending upon the number of properties of T, Add will continue

    return properties;
};

I know how to perform some of this, but what I am not sure on is how to create a local instance of a dictionary and then use it (and return it) in subsequent expressions?


Solution

  • It should be something like (comments inline):

    public static Func<T, Dictionary<string, object>> GetValuesFunc<T>()
    {
        Type objType = typeof(T);
    
        var dict = Expression.Variable(typeof(Dictionary<string, object>));
        var par = Expression.Parameter(typeof(T), "obj");
    
        var add = typeof(Dictionary<string, object>).GetMethod("Add", BindingFlags.Public | BindingFlags.Instance, null, new[] { typeof(string), typeof(object) }, null);
    
        var body = new List<Expression>();
        body.Add(Expression.Assign(dict, Expression.New(typeof(Dictionary<string, object>))));
    
        var properties = objType.GetTypeInfo().GetProperties(BindingFlags.Public | BindingFlags.Instance);
    
        for (int i = 0; i < properties.Length; i++)
        {
            // Skip write only or indexers
            if (!properties[i].CanRead || properties[i].GetIndexParameters().Length != 0)
            {
                continue;
            }
    
            var key = Expression.Constant(properties[i].Name);
            var value = Expression.Property(par, properties[i]);
            // Boxing must be done manually... For reference type it isn't a problem casting to object
            var valueAsObject = Expression.Convert(value, typeof(object));
            body.Add(Expression.Call(dict, add, key, valueAsObject));
        }
    
        // Return value
        body.Add(dict);
    
        var block = Expression.Block(new[] { dict }, body);
    
        var lambda = Expression.Lambda<Func<T, Dictionary<string, object>>>(block, par);
        return lambda.Compile();
    }
    

    use it like:

    public class Test
    {
        public int A { get; set; }
        public string B { get; set; }
    }
    

    and

    Func<Test, Dictionary<string, object>> fn = GetValuesFunc<Test>();
    
    var obj = new Test
    {
        A = 5,
        B = "Foo"
    };
    
    var res = fn(obj);