2015-11-22 4 views
2

У меня есть этот метод, с помощью Expression с для создания полей добытчиков:Expression.Convert (..., someGenericType) бросает ArgumentException при использовании общего типа

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; 

Так как я могу кормить его с общими типами? Я ограничена только не-generic-типами?

Edit - решение:

Kaveh Hadjari поймал:

Передача t = typeof (Dictionary<T, int>) не поднимет ArgumentException как t.GetGenericArguments()[0].IsGenericParameter является true

Передача типа t = typeof (Dictionary<int, int>) работает отлично, becuse нет (хотя t.GetGenericArguments()[1].IsGenericParameter это false!) элемент массива t.GetGenericArguments() имеет IsGenericParameter == true

+2

Ваш код не компилируется. 'type.CreateFieldValueGetter (type.GetField (" PropertyChanged "));' не компилируется, так как ваш метод не является методом расширения. * Однако *, исправляя это, ваш код работает отлично. Пожалуйста, покажите нам * точно * код, который вы используете. – Rob

+0

Невозможно воспроизвести проблему; кажется, работает отлично для меня. Возможно, вы слишком упростили этот пример? –

+0

См. Эту упрощенную скрипку, показывающую, что ваш метод 'CreateFieldValueGetter' действительно работает: https://dotnetfiddle.net/iVTzio –

ответ

1

Общий тип - это шаблон для множества различных специализированных типов, а во время выполнения существует разница между типичным типом и типами «instanced». Возможная причина, по которой вызов Expression.Convert может быть неудачным, может заключаться в том, что вы предоставляете ему тип универсальной версии, а не специализированную версию с набором переменных типа.

Обновление: Я предполагаю, что существует веская причина, по которой этот метод никогда не будет работать с универсальными типами. Рассмотрим случай, если переменная типа используется как тип для поля в родовом классе. Поскольку размер шрифта (reference, Boolean, short, int, long и т. Д.) Может быть переменным, это означало бы, что он мог бы смещать адрес памяти других полей в разных специализациях универсального класса переменным образом. Как вы знаете заранее, какая длина поля, например, смещение адреса, будет иметь место, если все переменные, которые не установлены? Вы не могли и поэтому не могли определить адрес поля, для которого мы хотели бы создать getter. Единственным решением было бы создать геттер, который полагался бы на использование рефлексии на каждом объекте, который вы назвали получателем, что принесло бы более высокие затраты, чем предполагалось, и если вы довольны этим решением, у вас может быть один метод, который получает значение поля, используя отражение, фактически не создавая эти геттеры в первую очередь.

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() не найдет его). Код, который вы показали, делает это неправильно, но он не объясняет, какое исключение вы получаете.