有没有一种方法可以将类型参数数组传递给泛型方法
本文关键字:数组 类型参数 泛型方法 方法 一种 有没有 | 更新日期: 2023-09-27 17:59:58
在处理OpenXml时,我遇到了这篇文章:如何:在电子表格文档中合并两个相邻的单元格(OpenXMLSDK)。
这里有一个代码示例,我想重构它。这是它的一部分:
// 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;
}
但我不知道该怎么做。
那么我有什么选择呢?
您可以为此创建一个流畅的API。结果可能允许这样的代码:
worksheet.InsertAfter<CustomSheetView>(mergeCells)
.Or<DataConsolidate>()
.Or<SortState>();
这种流畅的API有两个优点:
- 它很有表现力
- 它的工作原理不需要反思
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));
}
您想要的是他们在C++中所称的"类型列表"。然而,不幸的是,这些在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)
你可以实现类型列表来添加更多的技巧。例如,您可以将"head"(typeof(T1))与"tail"(其余部分作为Typelist)分离,并使Typelist仅处理第一个类型。使用类似的技巧,您可以迭代列表并为多种类型添加行为。
请注意,添加接口是为了添加限制,例如:
void InsertElementAfter<T>(...) where T:ITypelist
不幸的是,您不能将方法和类型作为"泛型"传递给方法(还不能?),所以您能做的最好的事情就是将它们作为字符串传递,并使用反射使其成为"真实"方法(使用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);
}
}
问题是这是否是个好主意。。。附加值是,您可以利用类型系统,并通过使用泛型来获得传递类型列表的能力,缺点是您有很多代码,但仍然必须使用反射。
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 });
}