MethodInfo, CreateDelegate и общие методы - PullRequest
4 голосов
/ 14 января 2010

Благодаря ответу Джона Скита в этом вопросе у меня работает следующее:

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: Ошибка привязки к целевому методу». на звонок 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();
  }
}

Как уже упоминалось выше, единственные различия, которые я вижу:

  1. Другой тип возврата (T работает, IEnumerable<T> не работает) [РЕДАКТИРОВАТЬ: это неправильно, первая версия использует BaseItem, а не T; упс]
  2. Перегрузки (GetItem не имеет перегрузок, GetList имеет несколько; мне нужен только делегат на GetList() без параметров

Update1: Сэм помог мне определить некоторые проблемы. Если возвращаемый тип делегата является общим (например, IEnumerable<BaseItem>), он задыхается, когда я пытаюсь поменять местами базовые / производные типы. Можно ли как-нибудь объявить мой метод GetList, как показано ниже? Мне нужно иметь возможность указать, что T наследуется от BaseItem, но если бы я мог, он бы работал нормально для меня.

public static IEnumerable<BaseItem> GetList<T>() where T : class

Другим вариантом будет «обобщение» моего объявления делегата. Все примеры, которые я могу найти, используют общий для параметров, а не тип возвращаемого значения. Как мне это сделать (выдает ошибку компилятора, причина T не определена и не позволяет использовать ограничение where):

public delegate IEnumerable<T> GetListDelegate();

Ответы [ 2 ]

2 голосов
/ 14 января 2010

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

1 голос
/ 14 января 2010

После внесения некоторых небольших изменений, чтобы получить второй пример для компиляции, я смог запустить его, и он получает и вызывает делегат нормально.

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 по-другому.

...