Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to pass an array of type parameters to a generic method?

Tags:

c#

generics

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?

like image 288
horgh Avatar asked Dec 27 '22 09:12

horgh


1 Answers

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:

  1. It is very expressive
  2. It works without reflection.

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));
}
like image 182
Daniel Hilgarth Avatar answered Apr 08 '23 22:04

Daniel Hilgarth