MethodInfo, CreateDelegate и общие методы
Благодаря ответу Джона Скита в этом вопросе у меня есть следующая работа:
public delegate BaseItem GetItemDelegate(Guid itemID);
public static class Lists
{
public static GetItemDelegate GetItemDelegateForType(Type derivedType)
{
MethodInfo method = typeof(Lists).GetMethod("GetItem");
method = method.MakeGenericMethod(new Type[] { derivedType });
return (GetItemDelegate)Delegate.CreateDelegate(typeof(GetItemDelegate), method);
}
public static T GetItem<T>(Guid itemID) where T : class { // returns an item of type T ... }
}
public class DerivedItem : BaseItem { }
// I can call it like so:
GetItemDelegate getItem = Lists.GetItemDelegateForType(typeof(DerivedItem));
DerivedItem myItem = getItem(someID); // this works great
Когда я пытаюсь применить то же самое к методу с другим типом возвращаемого значения и перегрузками (это единственные различия, которые я могу придумать), я получаю раздражающее "ArgumentException: Error binding to target method."по вызову в CreateDelegate
. Ниже приведен рабочий пример, который получает ошибку, просто скопируйте/вставьте в консольное приложение.
public delegate IEnumerable<BaseItem> GetListDelegate();
public class BaseItem { }
public class DerivedItem : BaseItem { }
public static class Lists
{
public static GetListDelegate GetListDelegateForType(Type itemType)
{
MethodInfo method = typeof(Lists).GetMethod("GetList", Type.EmptyTypes); // get the overload with no parameters
method = method.MakeGenericMethod(new Type[] { itemType });
return (GetListDelegate)Delegate.CreateDelegate(typeof(GetListDelegate), method);
}
// this is the one I want a delegate to, hence the Type.EmptyTypes above
public static IEnumerable<T> GetList<T>() where T : class { return new List<T>(0); }
// not the one I want a delegate to; included for illustration
public static IEnumerable<T> GetList<T>(int param) where T : class { return new List<T>(0); }
public static Type GetItemType()
{ // this could return any type derived from BaseItem
return typeof(DerivedItem);
}
}
class Program
{
static void Main(string[] args)
{
Type itemType = Lists.GetItemType();
GetListDelegate getList = Lists.GetListDelegateForType(itemType);
IEnumerable<BaseItem> myList = (IEnumerable<BaseItem>)getList();
}
}
Как упоминалось выше, только различия, которые я вижу, таковы:
- другой тип возврата (
T
работает,IEnumerable<T>
Нет) [EDIT: это неправильно, первая версия используетBaseItem
, а неT
; oops] - перегрузки (
GetItem
не имеет перегрузок,GetList
имеет несколько; Мне нужен только делегатGetList()
без параметров
Update1: Сэм помог мне определить некоторые проблемы. Если возвращаемый тип делегата является универсальным (например, IEnumerable<BaseItem>
), он задыхается, когда я пытаюсь поменять местами базовые / производные типы. Является есть ли способ, которым я могу объявить свой метод GetList
, как показано ниже? Я должен быть в состоянии указать, что T
наследует от BaseItem
, но если бы я мог, то это было бы прекрасно для меня.
public static IEnumerable<BaseItem> GetList<T>() where T : class
Другим вариантом было бы" обобщить " мое объявление делегата. Все примеры, которые я могу найти, используют универсальный для params, а не возвращаемый тип. Как это сделать (он выдает ошибку компилятора, Причина T
не определена, и он не позволяет мне использовать ограничение where
):
public delegate IEnumerable<T> GetListDelegate();
2 ответа:
Я добился этого, объявив делегата просто
IEnumerable
. Это позволяет ему создать делегат. Все, что оставалось тогда, было просто базовым кастингом. Приведенные ниже изменения исправляют второй блок кода выше.// declare this as non-generic public delegate IEnumerable GetListDelegate();
И
// do some cast-fu to get the list into a workable form List<BaseItem> myList = getList().Cast<BaseItem>().ToList();
Затем я могу делать
myList.Sort()
и все другие вещи, которые я пытаюсь делать в своей системе на работе.
После внесения некоторых незначительных изменений, чтобы получить второй пример для компиляции, я смог запустить его, и он получает и вызывает делегат нормально.
using System; using System.Collections.Generic; using System.Reflection; namespace ConsoleApplication1 { public delegate IEnumerable<BaseItem> GetListDelegate(); public class BaseItem { } public class DerivedItem : BaseItem { } public static class Lists { public static GetListDelegate GetListDelegateForType(Type derivedType) { MethodInfo method = typeof(Lists).GetMethod("GetList", Type.EmptyTypes); // get the overload with no parameters method = method.MakeGenericMethod(new[] { derivedType }); return (GetListDelegate)Delegate.CreateDelegate(typeof(GetListDelegate), method); // *** this throws an exception *** } // this is the one I want a delegate to, hence the Type.EmptyTypes above public static IEnumerable<T> GetList<T>() where T : class {// returns a collection of T items ... return new T[0]; } // not the one I want a delegate to; included for illustration, maybe my different GetMethod() is my problem? public static IEnumerable<T> GetList<T>(int param) where T : class { // returns a collection of T items ... return new T[0]; } } public class GenericDelegate { public static void Test() { // I would call it like so, but first line gets exception, where indicated above GetListDelegate getList = Lists.GetListDelegateForType(typeof(BaseItem)); IEnumerable<BaseItem> myList = getList(); } } }
Я не уверен, как вы получили свой второй пример для компиляции, хотя. Похоже, здесь есть проблема.
public delegate IEnumerable<BaseItem> GetListDelegate(); GetListDelegate getList = Lists.GetListDelegateForType(typeof(DerivedList)); IEnumerable<DerivedList> myList = getList();
Делегат объявляется как возвращающий IEnumerable, но затем вы вызываете его и присваиваете результат IEnumerable. Это не поддерживается в C# 3.5. Он находится в C# 4, но для этого потребуется объявить BaseItem / DerivedList по-другому объявить ковариацию (или контравариацию, я не уверен, что именно).