当与泛型类型一起使用时,Expression.Convert(...,someGenericType)会抛出ArgumentException

时间:2015-11-22 22:31:51

标签: c# generics lambda expression expression-trees

我有这个方法,使用Expression来创建字段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();
}

它很有用,直到我给它一个类似的通用类型:

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

这样:

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

这是我得到的例外:

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

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

虽然简单的铸造工作:

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

那么如何用泛型类型提供它呢?我只限于非通用类型吗?

编辑 - 解决方案:

Kaveh Hadjari抓住了它:

传递t = typeof (Dictionary<T, int>)会引发ArgumentExceptiont.GetGenericArguments()[0].IsGenericParametertrue(尽管t.GetGenericArguments()[1].IsGenericParameterfalse!)

传递类型t = typeof (Dictionary<int, int>)可以正常工作,因为t.GetGenericArguments()数组的元素没有IsGenericParameter == true

2 个答案:

答案 0 :(得分:2)

  

“我是否仅限于非通用类型?”

不,当然不是。错误消息很明确,但与您提供的代码示例不一致。您似乎传递了原始泛型类型定义的类型(即类型参数的未指定值),而不是构造的泛型类型(即具有类型参数的指定值)。

不幸的是,如果没有a good, minimal, complete code example可靠地再现问题,就不可能准确地知道你做错了什么。我只能说你确实做错了什么。如果您需要更具体的建议,请编辑您的帖子,以便它包含一个很好的代码示例。

对于它的价值,这里有一个完整的代码示例,演示了您的方法在泛型类型下正常工作:

class A<T>
{
    public int field1;
    public T field2;
    public event EventHandler Event1;
}

class Program
{
    static void Main(string[] args)
    {
        A<bool> a = new A<bool>();
        Func<object, int> field1Getter =
            CreateFieldValueGetter<int>(a.GetType(), a.GetType().GetField("field1"));
        Func<object, bool> field2Getter =
            CreateFieldValueGetter<bool>(a.GetType(), a.GetType().GetField("field2"));
        Func<object, EventHandler> event1Getter =
            CreateFieldValueGetter<EventHandler>(a.GetType(), a.GetType()
                .GetField("Event1", BindingFlags.NonPublic | BindingFlags.Instance));
    }

    static Func<object, T> CreateFieldValueGetter<T>(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();
    }
}

这里唯一的问题是,要获取事件的字段,您必须指定适合该字段的BindingFlags(特别是,它是非公开的,因此默认搜索GetField()赢了找不到它。您显示的代码不正确,但它不能解释您获得的异常。

答案 1 :(得分:2)

泛型类型是许多不同专用类型的模板,并且在运行时,泛型类型和&#34;实例类型之间存在差异。类型。调用Expression.Convert可能失败的一个可能原因可能是您为其提供了通用版本的类型,而不是设置了类型变量的专用版本。

更新:我想有一个很好的理由,这种方法永远不会适用于泛型类型。如果类型变量用作泛型类中的字段的类型,请考虑这种情况。由于类型大小(reference,Boolean,short,int,long等)可以是变量的,因此它意味着它可以以可变的方式偏移泛型类的不同特化中的其他字段的内存地址。如果未设置所有变量,您如何预先知道哪个字段长度因此地址偏移?你不可能,因此我们无法确定我们可能想要为其创建getter的字段的地址。唯一的解决方案是实际创建一个依赖于对你调用getter的每个对象使用反射的getter,这会产生比想象的更高的成本,如果你对这个解决方案感到满意,你可能也有一个方法,使用反射获取字段的值,而不是首先实际创建这些getter。