3

I’m trying to create expressions to access either fields or properties in a nested structure.

I managed to create both getters and setters (as lambda expressions) for fields and properties on a flat object. It works like so:

Delegate getter = getGetterExpression(objectType,"PropertyOrFieldName").Compile();
Delegate setter = getSetterExpression(objectType,"PropertyorFieldName").Compile();

I found this post (Marc Gravells answer) using a custom expression visitor to "chain" those lambda expressions to access nested objects. Is this the right way to do this (by chaining lambda expressions), if you have some deep (dynamic) nesting like the following example code? Or is there a more efficient way to achieve this?

// 'regular' C# Code
obj.PropA.FieldB.FieldC.PropD = "Hello World";

// targeted 'expression approach'
Delegate setter  = GetPathSetterLambda(obj.GetType(), "PropA.FieldB.FieldC.PropD").Compile();
setter.DynamicInvoke(obj, "Hello World!");

The getters and setters are created like this:

private static LambdaExpression getSetterExpression(Type objectType, string fieldOrPropertyName)
{
    ParameterExpression parameterExpression = Expression.Parameter(objectType);
    MemberExpression memberExpression = Expression.PropertyOrField(parameterExpression, fieldOrPropertyName);

    ParameterExpression valueParameterExpression = Expression.Parameter(memberExpression.Type);
    BinaryExpression assignExpression = Expression.Assign(memberExpression, valueParameterExpression);

    Type setterType = typeof(Action<,>).MakeGenericType(objectType, memberExpression.Type);

    return Expression.Lambda(setterType, assignExpression, parameterExpression, valueParameterExpression);
}

private static LambdaExpression getGetterExpression(Type objectType, string fieldOrPropertyName)
{
    ParameterExpression parameterExpression = Expression.Parameter(objectType);
    MemberExpression memberExpression = Expression.PropertyOrField(parameterExpression, fieldOrPropertyName);

    Type getterType = typeof(Func<,>).MakeGenericType(objectType, memberExpression.Type);

    return Expression.Lambda(getterType, memberExpression, parameterExpression);
}

I’m trying to do this mostly to improve the performance compared to using reflection.

Community
  • 1
  • 1
CSharpie
  • 9,195
  • 4
  • 44
  • 71
  • Why would you write something like that which uses *strings* to compile into expressions just to get a getter/setter? That makes no sense; you can use simple reflection for that. The whole point of expressions is that you can use *actual C#* syntax with IntelliSense and error detection instead of passing “magic” strings that have some arbitrary meaning. – poke Mar 13 '14 at 12:54
  • I aim to compile the Expression into a delegate for performancereasons. I bellieve, once this iscompiled into a single delegate, performance exceeds reflection. Am I wrong here? Also its not allways code first, so I have to deal with those magic strings. – CSharpie Mar 13 '14 at 13:07
  • I see what you mean, and I think you might be right about the performance. I’ll update my answer with an expression-based solution. – poke Mar 13 '14 at 13:16
  • @CSharpie The performance of this will *not* exceed reflection. All of this expression stuff is implemented using reflection, or at least completely comparable constructs, under the hood. – Servy Mar 13 '14 at 14:02
  • @servy you sure about that? First, I would answer performance questions by testing rather than assertion. Second, expression compile generates calls to the properties that are then jitter into the same code as any other call. – Eric Lippert Mar 13 '14 at 14:10
  • @EricLippert No, I'm not sure, but I couldn't imagine why it would need to be any different. Both are doing the same thing; if one can solve the problem much more quickly, I don't see why the other wouldn't be able to use the exact same solution to the problem. If compiling the expression really is that much faster, why wouldn't reflection do whatever the expression's `Compile` method is doing? – Servy Mar 13 '14 at 14:13
  • 1
    @Servy See this http://www.palmmedia.de/Blog/2012/2/4/reflection-vs-compiled-expressions-vs-delegates-performance-comparision Daniel Palme did the testing allready. – CSharpie Mar 13 '14 at 14:15
  • @servy compile runs a compiler. Compilers trade a slow analysis and codegen now for a delegate that runs fast later. – Eric Lippert Mar 13 '14 at 14:17
  • 1
    Note when reading those results that 123.456 is 123456. Europeans use the dot for grouping. I was momentarily very confused. :) – Eric Lippert Mar 13 '14 at 14:22
  • @EricLippert Would not `somePropertyInfo.GetGetMethod().CreateDelegate(...)`, using reflection, be comparable to compiling an expression, in that you do the work up front for fast execution time later? – Servy Mar 13 '14 at 14:30
  • 1
    @Servy: One would certainly imagine so, but imagination and reality are frequently different. As the guy who wrote much of the compiler for expression trees but not any of the compiler for the reflection layer, I only know half the story. I don't know what their performance goals were or what techniques they used to achieve them. – Eric Lippert Mar 13 '14 at 14:44
  • @CSharpie If you have found an answer to your question you should post it as an answer, rather than as an edit to the question. – Servy Mar 13 '14 at 16:23
  • Oh ok i thought this would be the case if i really solved it myself. – CSharpie Mar 13 '14 at 16:36
  • So after a few test it beacme clear that creating a delegate from propert info is faster than from expressions. However expressionperformance exceeds the one from the propertyinfo as soon as it comes to nesting. The propertyspeed is linear increase to the level of nesting, where the expression seems to doesnt really care at all. – CSharpie Mar 16 '14 at 15:18

2 Answers2

2

Compiling dynamic lambda expressions, so you get direct accessor and mutators for the object might really be more efficient than repeating reflection to access properties. While expressions are really not meant to be used like that, my implementation below just uses them internally to create direct delegates to read or set the values.

This is what I came up with. I made sure to not expose anything of the expressions to the outside, but just return simple delegates. Because of how lambda expressions work, they require the base type which I provide via the generic parameter. If you only have the type at run time in a Type object, you can easily change that, but you will also have to change the delegate types. I avoid having to specify the actual property type. In the accessor, I just return an object, and for the mutator, I make a type cast within the expression.

public static Func<T, object> GetAccessor<T>(string path)
{
    ParameterExpression paramObj = Expression.Parameter(typeof(T), "obj");

    Expression body = paramObj;
    foreach (string property in path.Split('.'))
    {
        body = Expression.PropertyOrField(body, property);
    }

    return Expression.Lambda<Func<T, object>>(body, new ParameterExpression[] { paramObj }).Compile();
}

public static Action<T, object> GetMutator<T>(string path)
{
    ParameterExpression paramObj = Expression.Parameter(typeof(T), "obj");
    ParameterExpression paramValue = Expression.Parameter(typeof(object), "value");

    Expression body = paramObj;
    foreach(string property in path.Split('.'))
    {
        body = Expression.PropertyOrField(body, property);
    }

    body = Expression.Assign(body, Expression.TypeAs(paramValue, body.Type));
    return Expression.Lambda<Action<T, object>>(body, new ParameterExpression[] { paramObj, paramValue }).Compile();
}

The implementation can be used like this:

obj.SomeB.SomeC.Foo = "bar";

var getter = GetAccessor<A>("SomeB.SomeC.Foo");
var setter = GetMutator<A>("SomeB.SomeC.Foo");

Console.WriteLine(getter(obj)); // "bar"
setter(obj, "baz");
Console.WriteLine(getter(obj)); // "baz"

As for errors, both methods will raise an ArgumentException for invalid paths when the delegates are created. Other than that, I don’t think there is anything that can go wrong. The mutator will silently set the value to null for invalid types.

The solution currently does not work for value types; you might have to add some additional type casting to make it work—or if that’s acceptable to you, add another type parameter for the property type.

Original answer

Using expressions does not really make sense here. The whole point of expressions is to have actual C# syntax, so you have IntelliSense, syntax validation, and type safety. By accepting strings of expressions, you do the exact reverse of that, and building expressions from that and compiling them just so you get access to getters and setters makes really no sense.

If you have strings that express the path, then you should just use standard reflection.

An example implementation (sans error detection for invalid property names or NULL values—you should add that!):

class PropertyAccessor
{
    private string[] properties;

    public PropertyAccessor (string path)
    {
        properties = path.Split('.');
    }

    private object GetValue (object obj, string property)
    {
        return obj.GetType().GetProperty(property).GetValue(obj);
    }

    private void SetValue (object obj, string property, object value)
    {
        return obj.GetType().GetProperty(property).SetValue(obj, value);
    }

    public object Get (object obj)
    {
        object o = obj;
        for (int i = 0; i < properties.Length; i++)
        {
            o = GetValue(o, properties[i]);
        }
        return o;
    }

    public void Set (object obj, object value)
    {
        object o = obj;
        for (int i = 0; i < properties.Length - 1; i++)
        {
            o = GetValue(o, properties[i]);
        }

        SetValue(o, properties[properties.Length - 1], value);
    }
}

Used like this:

obj.SomeB.SomeC.Foo = "bar";

var pa = new PropertyAccessor("SomeB.SomeC.Foo");

Console.WriteLine(pa.Get(obj)); // "bar"
pa.Set(obj, "baz");
Console.WriteLine(pa.Get(obj)); // "baz"
poke
  • 369,085
  • 72
  • 557
  • 602
  • My aim is to acchieve better performance (Ill edit my Question) over reflection. I doubt this is any faster then compiling the whole expression into a single delegate, or is it? Thoguh i see your poihnt of errorchecks 'on the way' which probably gets out of hands using expressions... – CSharpie Mar 13 '14 at 13:14
  • Thank you, this works. Now I try to tweak it abit and test it. – CSharpie Mar 13 '14 at 13:59
  • I updated my question with your solution tweaked to make valuetypes work aswell. And yes this is alot faster than Reflection. If someone really cares I can provide testresults. – CSharpie Mar 13 '14 at 16:02
0

Here my solution based on Poke's answer:

    public static LambdaExpression GetFieldOrPropertyLambda(PropertyOrFieldAccessType accessType, Type objectType, string fieldOrPropertyExpression)
    {
        ParameterExpression initialObjectParameterExpression = Expression.Parameter(objectType, objectType.Name);


        Expression pathExpression = initialObjectParameterExpression;

        foreach (string property in fieldOrPropertyExpression.Split('.'))
            pathExpression = Expression.PropertyOrField(pathExpression, property);

        LambdaExpression resultExpression;
        switch (accessType)
        {
            case PropertyOrFieldAccessType.Get:

                resultExpression = Expression.Lambda(
                    getGenericGetFunction(objectType, pathExpression.Type), // This makes it work for valueTypes.
                    pathExpression,
                    initialObjectParameterExpression);
                break;

            case PropertyOrFieldAccessType.Set:

                ParameterExpression assignParameterExpression = Expression.Parameter(pathExpression.Type);

                BinaryExpression assginExpression = Expression.Assign(pathExpression, assignParameterExpression);


                resultExpression = Expression.Lambda(
                    getGenericSetAction(objectType, assginExpression.Type), // This makes it work for valueTypes.
                    assginExpression,
                    initialObjectParameterExpression,
                    assignParameterExpression);
                break;

            default: throw new NotImplementedException();
        }

        return resultExpression;
    }

    private static Type getGenericGetFunction(Type param1, Type param2)
    {
        return typeof(Func<,>).MakeGenericType(param1, param2);
    }

    private static Type getGenericSetAction(Type param1, Type param2)
    {
        return typeof(Action<,>).MakeGenericType(param1, param2);
    }
CSharpie
  • 9,195
  • 4
  • 44
  • 71