Expression.Convert(..., someGenericType) 与泛型一起使用时抛出 ArgumentException
Expression.Convert(..., someGenericType) throws ArgumentException when used with generic type
我有这个方法,使用 Expression
s 创建字段 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;
那么我如何为它提供泛型类型呢?我是否仅限于非通用类型?
编辑 - 解决方案:
抓到了:
通过 t = typeof (Dictionary<T, int>)
将提高 ArgumentException
,因为 t.GetGenericArguments()[0].IsGenericParameter
是 true
(尽管 t.GetGenericArguments()[1].IsGenericParameter
是 false
!)
传递类型 t = typeof (Dictionary<int, int>)
工作正常,因为 t.GetGenericArguments()
数组的任何元素都没有 IsGenericParameter == true
"Am I limited to non-generic-types only?"
不,当然不是。错误消息很清楚,但与您提供的代码示例不一致。您似乎正在为类型传递原始泛型类型定义(即类型参数具有未指定的值),而不是构造的泛型类型(即类型参数具有指定值)。
不幸的是,没有 a good, minimal, complete code example 可靠地重现问题,就不可能准确地知道您做错了什么。我只能说你确实做错了什么。如果您需要比这更具体的建议,请编辑您的 post 以便它包含一个好的代码示例。
为了它的价值,这里有一个完整的代码示例,它演示了您的方法可以很好地处理泛型类型:
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
(特别是,它不是 public,因此默认搜索GetField()
不会找到它)。您显示的代码做错了,但它没有解释您遇到的异常。
泛型是许多不同专用类型的模板,在运行时泛型和 "instanced" 类型之间存在差异。调用 Expression.Convert 失败的一个可能原因可能是您为其提供了通用版本的类型,而不是带有类型变量集的专用版本。
更新:我想有一个很好的理由让这个方法永远无法处理泛型类型。考虑这种情况,如果类型变量用作泛型 class 中字段的类型。由于类型大小(引用、布尔值、short、int、long 等)可能是可变的,这意味着它可以以可变方式偏移泛型 class 不同特化中其他字段的内存地址。如果所有变量都未设置,您如何提前知道哪个字段长度以及地址偏移量?您不能,因此我们无法确定我们可能要为其创建 getter 的字段的地址。唯一的解决方案是实际创建一个 getter ,它依赖于对每个调用 getter 的对象使用反射,这会产生比想象的更高的成本,如果您对该解决方案感到满意,您可能也可以有一个单一的方法来使用反射获取字段的值,而无需首先实际创建这些 getter。
我有这个方法,使用 Expression
s 创建字段 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;
那么我如何为它提供泛型类型呢?我是否仅限于非通用类型?
编辑 - 解决方案:
通过 t = typeof (Dictionary<T, int>)
将提高 ArgumentException
,因为 t.GetGenericArguments()[0].IsGenericParameter
是 true
(尽管 t.GetGenericArguments()[1].IsGenericParameter
是 false
!)
传递类型 t = typeof (Dictionary<int, int>)
工作正常,因为 t.GetGenericArguments()
数组的任何元素都没有 IsGenericParameter == true
"Am I limited to non-generic-types only?"
不,当然不是。错误消息很清楚,但与您提供的代码示例不一致。您似乎正在为类型传递原始泛型类型定义(即类型参数具有未指定的值),而不是构造的泛型类型(即类型参数具有指定值)。
不幸的是,没有 a good, minimal, complete code example 可靠地重现问题,就不可能准确地知道您做错了什么。我只能说你确实做错了什么。如果您需要比这更具体的建议,请编辑您的 post 以便它包含一个好的代码示例。
为了它的价值,这里有一个完整的代码示例,它演示了您的方法可以很好地处理泛型类型:
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
(特别是,它不是 public,因此默认搜索GetField()
不会找到它)。您显示的代码做错了,但它没有解释您遇到的异常。
泛型是许多不同专用类型的模板,在运行时泛型和 "instanced" 类型之间存在差异。调用 Expression.Convert 失败的一个可能原因可能是您为其提供了通用版本的类型,而不是带有类型变量集的专用版本。
更新:我想有一个很好的理由让这个方法永远无法处理泛型类型。考虑这种情况,如果类型变量用作泛型 class 中字段的类型。由于类型大小(引用、布尔值、short、int、long 等)可能是可变的,这意味着它可以以可变方式偏移泛型 class 不同特化中其他字段的内存地址。如果所有变量都未设置,您如何提前知道哪个字段长度以及地址偏移量?您不能,因此我们无法确定我们可能要为其创建 getter 的字段的地址。唯一的解决方案是实际创建一个 getter ,它依赖于对每个调用 getter 的对象使用反射,这会产生比想象的更高的成本,如果您对该解决方案感到满意,您可能也可以有一个单一的方法来使用反射获取字段的值,而无需首先实际创建这些 getter。