【问题标题】:How to call a generic extension method dynamically?如何动态调用泛型扩展方法?
【发布时间】:2010-02-28 00:17:52
【问题描述】:

我写了这个扩展方法:

public static DataTable ToDataTable<T>(this IList<T> list)
{...}

如果使用编译时已知的类型调用它会很好:

DataTable tbl = new List<int>().ToDataTable();

但是如果不知道泛型类型怎么调用呢?

object list = new List<int>();
...
tbl = Extension.ToDataTable((List<object>)list); // won't work

【问题讨论】:

  • 你为什么投给List&lt;object&gt;?你的listList&lt;int&gt;,转换不会成功。
  • 因为他在编译时不知道他有什么类型的列表:他不知道它是List&lt;int&gt;。他试图通过强制转换为基类来解决它(正如您正确指出的那样,它不起作用,因为List&lt;int&gt;List&lt;object&gt; 不兼容,即使intobject 兼容)。

标签: c# generics extension-methods


【解决方案1】:

这是因为 List&lt;int&gt; 不是 List&lt;object&gt; —— List 类型在其元素类型参数中不是协变的。不幸的是,您需要获取泛型方法的类型化版本并使用反射调用它:

Type listItemType = typeof(int);   // cheating for simplicity - see below for real approach
MethodInfo openMethod = typeof(Extension).GetMethod("ToDataTable", ...);
MethodInfo typedMethod = openMethod.MakeGenericMethod(typeof(listItemType));
typedMethod.Invoke(null, new object[] { list });

另一种方法可能是创建一个接受IList 而不是IList&lt;T&gt; 的扩展方法版本。 List&lt;T&gt; 类实现了这个非泛型接口以及泛型接口,因此您可以调用:

public static DataTable WeakToDataTable(this IList list) { ... }

((IList)list).WeakToDataTable();

(实际上,您可能会使用重载而不是不同的名称——只是使用不同的名称来调用不同的类型。)


更多信息: 在反射解决方案中,我跳过了如何确定列表元素类型的问题。这可能有点棘手,具体取决于您想要获得的复杂程度。如果您假设该对象将是 List&lt;T&gt;(对于某些 T),那么这很容易:

Type listItemType = list.GetType().GetGenericArguments()[0];

如果您只愿意假设IList&lt;T&gt;,那就有点困难了,因为您需要找到合适的接口并从中获取通用参数。而且您不能使用 GetInterface() 因为您正在寻找一个通用接口的封闭构造实例。所以你必须通过所有接口来寻找IList&lt;T&gt;的实例:

foreach (Type itf in list.GetType().GetInterfaces())
{
  if (itf.IsGenericType && itf.GetGenericTypeDefinition == typeof(IList<>))  // note generic type definition syntax
  {
    listItemType = itf.GetGenericArguments()[0];
  }
}

这适用于空列表,因为它超出了元数据,而不是列表内容。

【讨论】:

  • 不只是投射到List&lt;int&gt; 而不是List&lt;object&gt; 解决问题吗?
  • 当然可以,但问题是“如果不知道泛型类型,如何调用它?” (强调补充)。因此我注意到,实际上他还必须使用反射找出 listItemType 而不是仅仅假设它是 int。
  • 我试过这个,但我有两个问题: 1. 如果列表为空,我将如何获取嵌入元素的类型? 2. 我有两个 ToDataTable() 扩展方法。如何获得 IList 的那个?
  • Alex:从你的回答看来,你已经选择了非通用的IList 解决方案并使用重载解决了问题,所以我猜你不再需要这两个问题的答案问题——如果你愿意,请留下另一条评论。
  • alex:我已经更新了答案来回答你的第一个问题。不确定您的第二个问题 - 您要区分哪些扩展方法?
【解决方案2】:

在使用IList&lt;T&gt; 接口遇到问题后,我使用IList 接口解决了它,例如itowlson 建议。由于 _T 方法,它有点难看,但效果很好:

DataTable tbl = ((IList)value).ToDataTable();

public static class Extensions
{
    private static DataTable ToDataTable(Array array) {...}
    private static DataTable ToDataTable(ArrayList list) {...}
    private static DataTable ToDataTable_T(IList list) {...}

    public static DataTable ToDataTable(this IList list)
    {
        if (list.GetType().IsArray)
        {
            // handle arrays - int[], double[,] etc.
            return ToDataTable((Array)list);
        }
        else if (list.GetType().IsGenericType)
        {
            // handle generic lists - List<T> etc.
            return ToDataTable_T(list);
        }
        else
        {
            // handle non generic lists - ArrayList etc.
            return ToDataTable((ArrayList)list);
        }            
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多