Reputation: 18534
Working over OpenXml, I've came across this article: How to: Merge two adjacent cells in a spreadsheet document (Open XML SDK).
There is a code sample there, that I wanted to refactor. Here is its part:
// 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
The best thing I managed is an extension method:
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;
}
}
But its usage looks as much terrible as the original code:
if (!worksheet.InsertElementAfter<CustomSheetView>(mergeCells))
if (!worksheet.InsertElementAfter<DataConsolidate>(mergeCells))
if (!worksheet.InsertElementAfter<SortState>(mergeCells))
//...and 5 more
If I could somehow declare an array (or something) of type parameters, I would be able to write something like this:
foreach (var T in typeParameterList)
{
if (worksheet.InsertElementAfter<T>(mergeCells))
break;
}
But I do not know any way to do this.
So what are my options?
Upvotes: 4
Views: 3520
Reputation: 3059
Reflections can help you invoke the method at runtime with correct type.
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 });
}
Upvotes: 2
Reputation: 174309
You could create a fluent API for this. The result could allow code like this:
worksheet.InsertAfter<CustomSheetView>(mergeCells)
.Or<DataConsolidate>()
.Or<SortState>();
There are two advantages to this fluent API:
The implementation of the API would require a class that holds the values and the Or()
method:
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;
}
}
The InsertAfter
extension method starts this chain and looks like so:
public static ChainedElementInserter InsertAfter<T>(this Worksheet worksheet,
OpenXmlElement element)
where T : OpenXmlElement
{
return new ChainedElementInserter(
worksheet, element, worksheet.InsertElementAfter<T>(element));
}
Upvotes: 4
Reputation: 31116
What you're looking for is what they call in C++ a "typelist". However, these are unfortunately not supported in C#.
What you can do is simulate this behavior by creating a bunch of classes with various type arguments and making them 'recursive' as follows:
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
Then you can use that to pass lists of types:
worksheet.InsertElementAfter<Typelist<T1, T2, T3>>(mergeCells)
You can implement the typelist to add more trickery. For example, you can split 'head' (typeof(T1)) from 'tail' (the rest as a Typelist) and make the typelist only handle the first type. Using tricks like that you can iterate the list and add behavior for multiple types.
Note that the interface is added so that you can add a restriction, e.g.:
void InsertElementAfter<T>(...) where T:ITypelist
...Unfortunately you cannot pass methods and types as 'generic' to a method (yet?), so best you can do is to pass them as string and use reflection to make it a 'real' method (using MakeGenericType / ...).
Eventually you will end up with a big helper class that looks something like this:
// ...
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);
}
}
Question is if this is a good idea or not... The added value is that you can exploit the type system and obtain the ability to pass lists of types by using generics, the downside is that you have a lot of code and still have to use reflection.
Upvotes: 4