Как вызвать универсальный метод расширения динамически? - PullRequest
7 голосов
/ 28 февраля 2010

Я написал этот метод расширения:

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

Ответы [ 2 ]

11 голосов
/ 28 февраля 2010

Это происходит потому, что List<int> не является List<object> - тип списка не является ковариантным в параметре типа элемента. К сожалению, вам необходимо получить типизированную версию универсального метода и вызвать его с помощью отражения:

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<T>. Класс List<T> реализует этот неуниверсальный интерфейс, а также универсальный интерфейс, поэтому вы сможете вызывать:

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

((IList)list).WeakToDataTable();

(В действительности вы, вероятно, использовали бы перегрузку, а не другое имя - просто использовали бы другое имя для вызова различных типов.)


Дополнительная информация: В решении с отражением я пропустил проблему определения типа элемента списка. Это может быть немного сложно в зависимости от того, насколько сложным вы хотите получить. Если вы предполагаете, что объект будет List<T> (для некоторого T), тогда это просто:

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

Если вы только предполагаете, что IList<T>, то это немного сложнее, потому что вам нужно найти соответствующий интерфейс и получить общий аргумент из этого. И вы не можете использовать GetInterface (), потому что вы ищете закрытый созданный экземпляр универсального интерфейса. Таким образом, вы должны просматривать все интерфейсы, ища один, который является экземпляром IList<T>:

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

Это будет работать для пустых списков, потому что они удаляются из метаданных, а не из содержимого списка.

0 голосов
/ 28 февраля 2010

После проблем с настройкой интерфейса IList<T> я решил проблему с помощью интерфейса 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);
        }            
    }
}
...