有没有办法将类型参数数组传递给泛型方法?

时间:2013-02-19 09:26:47

标签: c# generics

使用OpenXml,我发现了这篇文章:How to: Merge two adjacent cells in a spreadsheet document (Open XML SDK)

那里有一个代码示例,我想重构。这是它的一部分:

// Insert a MergeCells object into the specified position.
if (worksheet.Elements<CustomSheetView>().Count() > 0)
{
     worksheet.InsertAfter(mergeCells, 
                           worksheet.Elements<CustomSheetView>().First());
}
else if (worksheet.Elements<DataConsolidate>().Count() > 0)
{
     worksheet.InsertAfter(mergeCells, 
                           worksheet.Elements<DataConsolidate>().First());
}
else if (worksheet.Elements<SortState>().Count() > 0)
{
     worksheet.InsertAfter(mergeCells, 
                           worksheet.Elements<SortState>().First());
}
//...and 5 more 

我管理的最好的事情是扩展方法:

public static bool InsertElementAfter<T>(this Worksheet worksheet, 
                                              OpenXmlElement element)
    where T : OpenXmlElement
{
    if (!worksheet.Elements<T>().Any())
        return false;
    else
    {
        worksheet.InsertAfter(element, worksheet.Elements<T>().First());
        return true;
    }
}

但它的用法看起来和原始代码一样糟糕:

if (!worksheet.InsertElementAfter<CustomSheetView>(mergeCells))
    if (!worksheet.InsertElementAfter<DataConsolidate>(mergeCells))
        if (!worksheet.InsertElementAfter<SortState>(mergeCells))
            //...and 5 more

如果我能以某种方式声明一个类型参数的数组(或其他东西),我就能写出这样的东西:

foreach (var T in typeParameterList)
{
    if (worksheet.InsertElementAfter<T>(mergeCells))
        break;
}

但我不知道如何做到这一点。

那么我的选择是什么?

3 个答案:

答案 0 :(得分:4)

您可以为此创建一个流畅的API。结果可能允许这样的代码:

worksheet.InsertAfter<CustomSheetView>(mergeCells)
         .Or<DataConsolidate>()
         .Or<SortState>();

这种流畅的API有两个优点:

  1. 非常富有表现力
  2. 没有反思就行了。
  3. API的实现需要一个包含值和Or()方法的类:

    public class ChainedElementInserter
    {
        OpenXmlElement _element;
        Worksheet _worksheet;
        bool _previousResult;
    
        // ctor that initializes all three fields goes here.
    
        public ChainedElementInserter Or<T>()
            where T : OpenXmlElement
        {
            if (!_previousResult)
                _previousResult = _worksheet.InsertElementAfter<T>(_element);
    
            return this;
        }
    }
    

    InsertAfter扩展方法启动此链,看起来像这样:

    public static ChainedElementInserter InsertAfter<T>(this Worksheet worksheet, 
                                      OpenXmlElement element)
        where T : OpenXmlElement
    {
        return new ChainedElementInserter(
            worksheet, element, worksheet.InsertElementAfter<T>(element));
    }
    

答案 1 :(得分:3)

您正在寻找的是他们在C ++和#34;类型列表&#34;中所称的内容。但是,遗憾的是,C#不支持这些。

你可以做的是通过创建一堆具有各种类型参数的类并使它们“递归”来模拟这种行为。如下:

public interface ITypelist { Type[] List { get; } }
public class Typelist<T1> : ITypelist {
    public Type[] List { get { return new Type[]{typeof(T1)}; }}
}
public class Typelist<T1, T2> : ITypelist {
    public Type[] List { get { return new Type[]{typeof(T1), typeof(T2)}; }}

}
// etc

然后您可以使用它来传递类型列表:

worksheet.InsertElementAfter<Typelist<T1, T2, T3>>(mergeCells)

您可以实施类型列表以添加更多技巧。例如,您可以拆分“头部”。 (typeof(T1))来自&#39; tail&#39; (其余作为Typelist)并使类型列表仅处理第一种类型。使用这样的技巧,您可以迭代列表并添加多种类型的行为。

请注意,添加了界面以便您可以添加限制,例如:

void InsertElementAfter<T>(...) where T:ITypelist

...很遗憾,您无法将方法和类型传递为&#39; generic&#39;对于一种方法(还是?),你最好能做的就是将它们作为字符串传递并使用反射来使它成为一个真实的&#39;方法(使用MakeGenericType / ...)。

最终你会得到一个看起来像这样的大助手类:

// ...

public class Typelist<T1, T2> : ITypelist
{
    public Type MakeGenericType(Type t)
    {
        return t.MakeGenericType(typeof(T1));
    }

    public MethodInfo MakeGenericMethod(MethodInfo method)
    {
        return method.MakeGenericMethod(typeof(T1));
    }

    public Type Head { get { return typeof(T1); } }
    public Typelist<T2> Tail { get { return new Typelist<T2>(); } }

    public Type[] List { get { return new Type[] { typeof(T1), typeof(T2) }; } }
}

// ...

public static class Ext
{
    public static void InvokeAll<T1>(this Typelist<T1> typelist, MethodInfo baseMethod, object obj, object[] pars)
    {
        typelist.MakeGenericMethod(baseMethod).Invoke(obj, pars);
        // tail so no recursion
    }

    public static void InvokeAll<T1, T2>(this Typelist<T1, T2> typelist, MethodInfo baseMethod, object obj, object[] pars)
    {
        typelist.MakeGenericMethod(baseMethod).Invoke(obj, pars);
        InvokeAll(typelist.Tail, baseMethod, obj, pars);
    }
}

问题是,这是否是一个好主意...增加的价值是你可以利用类型系统并获得使用泛型传递类型列表的能力,缺点是你有很多代码并且仍然必须使用反射。

答案 2 :(得分:2)

Reflections可以帮助您在运行时使用正确的类型调用该方法。

Type[] typeParamList = new Type[] { typeof(CustomSheetView), typeof(DataConsolidate) } //And 9 more

MethodInfo method = typeof(Extensions).GetMethod("InsertElementAfter");
foreach (var type in typeParamList)
{
    var genericMethod = method.MakeGenericMethod(new Type[] { type });
    genericMethod.Invoke(null, new object[] { worksheet, mergeCells });
}