Question

I'm creating and caching property accessor delegates using the following code:

    static Delegate CreateGetterDelegate<T>(PropertyInfo propertyInfo)
    {
        if (typeof(T) != propertyInfo.DeclaringType)
        {
            throw new ArgumentException();
        }

        var instance = Expression.Parameter(propertyInfo.DeclaringType);
        var property = Expression.Property(instance, propertyInfo);
        var convert = Expression.TypeAs(property, typeof(object));
        return (Func<T, object>)Expression.Lambda(convert, instance).Compile();
    }

This is working and working well (thank you StackOverflow!), however I'd like to remove the boxing/unboxing required by returning a Func of T and object. Is there any way to change the return so that the return type is a Func of T and typeofproperty ?

I.e.

    static Delegate CreateGetterDelegate<T>(PropertyInfo propertyInfo)
    {
        if (typeof(T) != propertyInfo.DeclaringType)
        {
            throw new ArgumentException();
        }

        ... some magic happening here ...

        return (Func<T, typeofproperty>)Expression.Lambda(...more magic...).Compile();
    }

Note - I'm using VS2013 and .NET 4.5

Was it helpful?

Solution

The only way to do that is for the property's type to be known at compile time, either within this method definition, or for the caller. If the caller of this method knows what the type of the property is, then it can specify it using a second generic argument:

static Func<TInstance, TResult> CreateGetterDelegate<TInstance, TResult>(
    PropertyInfo propertyInfo)
{
    if (typeof(TInstance) != propertyInfo.DeclaringType)
    {
        throw new ArgumentException();
    }

    var instance = Expression.Parameter(propertyInfo.DeclaringType);
    var property = Expression.Property(instance, propertyInfo);
    var convert = Expression.TypeAs(property, typeof(object));
    return (Func<TInstance, TResult>)Expression.Lambda(convert, instance)
        .Compile();
}

Of course, this is just pushing the problem back to the caller. If even the caller isn't going to know what the type of the property is, then they have the exact same problem.

At the end of the day to avoid the boxing some code somewhere needs to know at compile time what the type of this property is. If someone, somewhere, will know at compile time, then you can just keep punting on solving the problem by using generics until you get to that point where someone can just use the actual known type. If nothing ever does know the type at compile time, then there really isn't any solution to this problem.

You could possibly, if you want to get technical, avoid boxing by making things even more dynamic than they are (using reflection to call these methods that are using reflection to do stuff) but while you may, from a technical standpoint, be able to avoid the literal box command, you'd be losing out way more than you'd gained. It wouldn't be a practical solution.

OTHER TIPS

The comments above about using reflection once again got me thinking (always a dangerous prospect) and I've come up with the following solution, basically implementing Servy's suggestion:

    static Delegate CreateGenericGetterDelegate<TClass>(PropertyInfo propertyInfo)
    {
        var method = typeof(Cache).GetMethod("CreateTypedGetterDelegate"); 
        MethodInfo generic = method.MakeGenericMethod(new Type[] { typeof(TClass), propertyInfo.PropertyType });
        return (Delegate) generic.Invoke(new object(), new object[] { propertyInfo });
    }

    public static Delegate CreateTypedGetterDelegate<TClass, TProp>(PropertyInfo propertyInfo)
    {
        if (typeof(TClass) != propertyInfo.DeclaringType)
        {
            throw new ArgumentException();
        }

        var instance = Expression.Parameter(propertyInfo.DeclaringType);
        var property = Expression.Property(instance, propertyInfo);
        if (typeof(TProp).IsValueType)
        {
            return (Func<TClass, TProp>)Expression.Lambda(property, instance).Compile();
        }
        else
        {
            return (Func<TClass, TProp>)Expression.Lambda(Expression.TypeAs(property, typeof(TProp)), instance).Compile();
        }
    }

This works, provides type-safe access and avoids any obvious boxing/unboxing. Since this is happening once at the beginning of my program (and only for properties being actually accessed), I'm ok with the performance hit for reflection, in return for typed delegates for property accessors.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top