MethodInfo、CreateDelegate和泛型方法

5

感谢Jon Skeet在这个问题中的回答,我现在已经有了以下的工作方案:

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

当我试图把同样的东西应用到一个有不同返回类型和重载方法的函数时(这是我能想到的唯一区别),我在调用CreateDelegate时会得到一个让人恼火的“ArgumentException:Error binding to target method。”下面是一个可工作但会出错的示例代码,只需将其复制/粘贴到控制台应用程序中即可。

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() 委托
更新1: Sam 帮助我找出了一些问题。如果委托的返回类型是泛型的(例如 IEnumerable<BaseItem>),当我尝试交换基类/派生类时会出现问题。有没有办法像下面这样声明我的 GetList 方法?我需要能够表示 T 继承自 BaseItem,但如果我能够这样做,那对我来说就可以正常工作了。
public static IEnumerable<BaseItem> GetList<T>() where T : class

另一种选择是将我的委托声明“泛型化”。我能找到的所有示例都使用参数的泛型,而不是返回类型的泛型。我该怎么做?(它会抛出编译器错误,因为未定义 T,并且它不允许我使用 where 约束)
public delegate IEnumerable<T> GetListDelegate();

在发布这篇文章之前,我阅读了很多资料,了解到类型不匹配是引发此异常的常见原因之一。我在上面的每个示例中的返回调用处设置了断点,并且每种情况下变量方法的类型似乎几乎相同。我没有看到任何问题。只是想补充一下这个细节。 - sliderhouserules
为什么这个被改成了社区维基? - sliderhouserules
1
哇,你编辑自己的帖子足够多次,它就会自动变成维基了?这太愚蠢了。 - sliderhouserules
2个回答

3

我通过将委托声明为 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

对第二个示例进行了一些小修改后,我成功地运行了它,并且它可以获取并调用委托。

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以声明协变性(或逆变性,我不确定哪个)。

我从我的工作大系统中提取了所有这些代码,并更改了类型名称等内容,以制作可发布的示例。我应该先制作一个示例应用程序并进行测试。将DerivedList类型更改为DerivedItem。这是一个复制/粘贴/替换错误。我已经编辑了我的问题来解决这个问题。考虑到这一点,你能否重新陈述一下你最后一段的意思,因为我没有跟上你的思路。 - sliderhouserules

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接