Creating an performant open delegate for an property setter or getter
Asked Answered
G

3

15

An open delegate is a delegate to an instance method without the target. To call it you supply the target as its first parameter. They are a clever way to optimize code that otherwise would use reflection and have poor performance. For an intro to open delegates see this. The way you would use it in practice is to have expensive reflection code to build these open delegates, but then you would be able to call them very cheaply as a simple Delegate call.

I'm trying to write code that will transform an arbitrary PropertyInfo, into such an delegate for its setter. So far I came up with this:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace Test
{
    class TestClass
    {
        static Action<T, object> MakeSetterDelegate<T>(PropertyInfo property)
        {
            MethodInfo setMethod = property.GetSetMethod();
            if (setMethod != null && setMethod.GetParameters().Length == 1) //skips over nasty index properties
            {
                //To be able to bind to the delegate we have to create a delegate 
                //type like: Action<T,actualType> rather than Action<T,object>.
                //We use reflection to do that
                Type setterGenericType = typeof(Action<,>);
                Type delegateType = setterGenericType.MakeGenericType(new Type[] { typeof(T), property.PropertyType });
                var untypedDelegate = Delegate.CreateDelegate(delegateType, setMethod);

                //we wrap the Action<T,actualType> delegate into an Action<T,object>
                Action<T, object> setter = (instance, value) =>
                {
                    untypedDelegate.DynamicInvoke(new object[] { instance, value });
                };
                return setter;
            }
            else
            {
                return null;
            }
        }

        int TestProp 
        {
            set
            {
                System.Diagnostics.Debug.WriteLine("Called set_TestProp");
            }
        }

        static void Test() 
        {
            PropertyInfo property = typeof(TestClass).GetProperty("TestProp");
            Action<TestClass, object> setter = MakeSetterDelegate<TestClass>(property);
            TestClass instance = new TestClass();
            setter(instance, 5);
        }
    }
}

Similar code would be written for the getter. It works, but the setter delegate uses a DynamicInvoke to convert from an Action<derivedType> to Action<object>, which I suspect is eating a good part of the optimization I'm after. So the questions are:

  1. Is the DynamicInvoke a real concern?
  2. Is there anyway around it?
Ganja answered 3/11, 2010 at 9:51 Comment(0)
B
20

DynamicInvoke will not make a performant setter. Reflection against a generic inner type is your better option here, as this will allow you to use typed delegates. Another option is DynamicMethod, but then you need to worry about a few IL details.

You might want to look at HyperDescriptor, which wraps up the IL work into a PropertyDescriptor implementation. Another option is the Expression API (if you are using .NET 3.5 or above):

static Action<T, object> MakeSetterDelegate<T>(PropertyInfo property)
{
    MethodInfo setMethod = property.GetSetMethod();
    if (setMethod != null && setMethod.GetParameters().Length == 1)
    {
        var target = Expression.Parameter(typeof(T));
        var value = Expression.Parameter(typeof(object));
        var body = Expression.Call(target, setMethod,
            Expression.Convert(value, property.PropertyType));
        return Expression.Lambda<Action<T, object>>(body, target, value)
            .Compile();
    }
    else
    {
        return null;
    }
}

Or alternatively with a generic type:

    abstract class Setter<T>
    {
        public abstract void Set(T obj, object value);
    }
    class Setter<TTarget, TValue> : Setter<TTarget>
    {
        private readonly Action<TTarget, TValue> del;
        public Setter(MethodInfo method)
        {
            del = (Action<TTarget, TValue>)
                Delegate.CreateDelegate(typeof(Action<TTarget, TValue>), method);
        }
        public override void Set(TTarget obj, object value)
        {
            del(obj, (TValue)value);
        }

    }
    static Action<T, object> MakeSetterDelegate<T>(PropertyInfo property)
    {
        MethodInfo setMethod = property.GetSetMethod();
        if (setMethod != null && setMethod.GetParameters().Length == 1)
        {
            Setter<T> untyped = (Setter<T>) Activator.CreateInstance(
                typeof(Setter<,>).MakeGenericType(typeof(T),
                property.PropertyType), setMethod);
            return untyped.Set;
        }
        else
        {
            return null;
        }
    }
Backed answered 3/11, 2010 at 9:57 Comment(8)
Can you elaborate on your answer? 1-What do you mean by "Reflection against a generic inner type"; 2 - How would the Expression API help me?Ganja
@David - expression example added. I'll whip up a generic inner type exampleBacked
@David - and added inner generic type exampleBacked
Awesome sample. Should the compiled lambda be as performant as an simple delegate call wrapping the setter?Ganja
Also how would you compare both implementations?Ganja
@David - the compiled lambda is a simple delegate call wrapping the setter (with all the casts, etc)Backed
@David - re comparing them: probably with Stopwatch and about 5M iterations.Backed
What is actually the point of wrapping the delegate with an expression? If the expression calls the delegate... I can just call the delegate myslef, can't I? What's the difference in execution time? Has anyone measured it yet?Mure
N
1

I once made this class. Perhaps it helps:

public class GetterSetter<EntityType,propType>
{
    private readonly Func<EntityType, propType> getter;
    private readonly Action<EntityType, propType> setter;
    private readonly string propertyName;
    private readonly Expression<Func<EntityType, propType>> propertyNameExpression;

    public EntityType Entity { get; set; }

    public GetterSetter(EntityType entity, Expression<Func<EntityType, propType>> property_NameExpression)
    {
        Entity = entity;
        propertyName = GetPropertyName(property_NameExpression);
        propertyNameExpression = property_NameExpression;
        //Create Getter
        getter = propertyNameExpression.Compile();
        // Create Setter()
        MethodInfo method = typeof (EntityType).GetProperty(propertyName).GetSetMethod();
        setter = (Action<EntityType, propType>)
                 Delegate.CreateDelegate(typeof(Action<EntityType, propType>), method);
    }


    public propType Value
    {
        get
        {
            return getter(Entity);
        }
        set
        {
            setter(Entity, value);
        }
    }

    protected string GetPropertyName(LambdaExpression _propertyNameExpression)
    {
        var lambda = _propertyNameExpression as LambdaExpression;
        MemberExpression memberExpression;
        if (lambda.Body is UnaryExpression)
        {
            var unaryExpression = lambda.Body as UnaryExpression;
            memberExpression = unaryExpression.Operand as MemberExpression;
        }
        else
        {
            memberExpression = lambda.Body as MemberExpression;
        }
        var propertyInfo = memberExpression.Member as PropertyInfo;
        return propertyInfo.Name;
    }

test:

var gs = new GetterSetter<OnOffElement,bool>(new OnOffElement(), item => item.IsOn);
        gs.Value = true;
        var result = gs.Value;
Nobby answered 9/12, 2010 at 10:2 Comment(0)
T
1

This is an old question, but someone may still find the following answer useful.

  1. I think the DynamicInvoke call makes the whole idea of optimising via MakeSetterDelegate useless. The result will be almost equivalent to just using PropertyInfo.SetValue (in terms of performance).

  2. The MakeSetterDelegate method from the question is based on using of the Delegate.CreateDelegate API and this is the right way. A couple of changes to the original idea will give us the desired solution. Adding a generic parameter representing a property's type, separating the method and using the MethodInfo.MakeGenericMethod reflection API. Here is the resulting code (with additional explainations):

     static Action<T, object> MakeSetterDelegate<T>(PropertyInfo property)
     {
         // All the actual work is done in the MakeSetterDelegateCore<T, TValue> method below.
         // We can't call it directly because of an unknown second generic type argument.
         // So, we take advantage of the MethodInfo.MakeGenericMethod reflection API.
         // First, we need to get the MethodInfo instance,
         //   which is actually a so-called "GenericMethodDefinition".
         // Of course, we can use a typeof(TestClass).GetMethod(...) call to do this,
         //   but there is a more interesting, type-safe and reliable way – via delegate,
         //   which is demonstrated here:
         var method = ((Func<PropertyInfo, Action<T, object>>)MakeSetterDelegateCore<T, object>)
             .Method
             .GetGenericMethodDefinition()
             .MakeGenericMethod(new[] { typeof(T), property.PropertyType });
    
         // The performance cost of the MethodInfo.Invoke call is not an issue here:
         //   this is a one-time call compared to multiple uses of the resulting delegate.
         return (Action<T, object>)method.Invoke(null, new object[] { property });
     }
    
     private static Action<T, object> MakeSetterDelegateCore<T, TValue>(PropertyInfo property)
     {
         MethodInfo setMethod = property.GetSetMethod();
         if (setMethod != null && setMethod.GetParameters().Length == 1) //skips over nasty index properties
         {
             var typedSetter = (Action<T, TValue>)Delegate.CreateDelegate(typeof(Action<T, TValue>), setMethod);
             Action<T, object> setter = (instance, value) =>
             {
                 typedSetter(instance, (TValue)value);
             };
             return setter;
         }
    
         return null; // or perhaps it's better to throw some exception
     }
    
Torbert answered 30/3 at 21:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.