Search code examples
c#genericslambdaexpressionexpression-trees

Expression.Convert(..., someGenericType) throws ArgumentException when used with generic type


I have this method, using Expressions to create fields getters:

public static Func<object, T> CreateFieldValueGetter<T>(this Type declaringType, FieldInfo fieldToGet) {
    var paramExp = Expression.Parameter(typeof(object));
    // ArgumentException if declaringType describes generic-type:
    var cast = Expression.Convert(paramExp, declaringType);
    var body = Expression.Field(cast, fieldToGet);
    return Expression.Lambda<Func<object, T>>(body, paramExp).Compile();
}

It works great until I give it a generic type like:

class DataErrorNotifyingViewModelBase<TErr> : ViewModelBase, INotifyDataErrorInfo 
    where TErr : struct, IConvertible, IComparable, IFormattable
{
    // ...
}

This way:

var vm = new DataErrorNotifyingViewModelBase<MyErrorsTypeEnum> ();
var type = vm.GetType();
// ArgumentException:
var getter = type.CreateFieldValueGetter<PropertyChangedEventHandler>(type.GetField("PropertyChanged"));

This is the exception I get:

Exception thrown: 'System.ArgumentException' in System.Core.dll

Additional information: Type GuiHelpers.DataErrorNotifyingViewModelBase`1[TErr] is a generic type definition

although simple casting works:

var vm = new DataErrorNotifyingViewModelBase<PrintDialogError>();
var obj = (object) vm;

So how can I feed it with generic types? Am I limited to non-generic-types only?

Edit - solution:

Kaveh Hadjari caught it:

Passing t = typeof (Dictionary<T, int>) will raise ArgumentException, as t.GetGenericArguments()[0].IsGenericParameter is true (albeit t.GetGenericArguments()[1].IsGenericParameter is false!)

Passing the type t = typeof (Dictionary<int, int>) works fine, becuse no element of the t.GetGenericArguments() array has IsGenericParameter == true


Solution

  • A generic type is a template for many different specialized types, and at runtime theres a difference between the generic type and the "instanced" types. A possible reason the call to Expression.Convert might be failing could be you're providing it with the type of the generic version and not with a specialized version with type variables set.

    Update: I imagine there's a good reason this method would never work with generic types. Consider the case if the type variable is used as type for a field in the generic class. Since the type size (reference, Boolean, short, int, long, etc) could be variable it would mean that it could offset the memory address of other fields in different specializations of the generic class in a variable way. How would you know in advance which field length thus address offset would be the case if all the variables where not set? You couldn't and therefor we can't determine the the address of the field we might want to create a getter for. The only solution would be to actually create a getter that would rely on using reflection on each object you call the getter with, which would incur higher costs than imagined and if you are happy with that solution you might be as well have a single method that gets the value of the field using reflection without actually creating these getters in the first place.