views:

244

answers:

2

I wrote this extension method:

public static DataTable ToDataTable<T>(this IList<T> list)
{...}

It works well if called with a type known at compile time:

DataTable tbl = new List<int>().ToDataTable();

But how to call it if the generic type isn't known?

object list = new List<int>();
...
tbl = Extension.ToDataTable((List<object>)list); // won't work
+3  A: 

This occurs because a List<int> is not a List<object> -- the List type is not covariant in its element type parameter. Unfortunately you would need to get a typed version of the generic method and call it using reflection:

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 });

An alternative may be to create a version of your extension method that accepts IList rather than IList<T>. The List<T> class implements this non-generic interface as well as the generic interface, so you will be able to call:

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

((IList)list).WeakToDataTable();

(In reality you'd probably use an overload rather than a different name -- just using a different name to call out the different types.)


More info: In the reflection solution, I skipped over the problem of how to determine the list element type. This can be a bit tricky depending on how sophisticated you want to get. If you're assuming that the object will be a List<T> (for some T) then it's easy:

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

If you're only willing to assume IList<T> then it's a bit harder, because you need to locate the appropriate interface and get the generic argument from that. And you can't use GetInterface() because you're looking for a closed constructed instance of a generic interface. So you have to grovel through all the interfaces looking for one which is an instance of 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];
  }
}

This will work for empty lists because it goes off the metadata, not the list content.

itowlson
Would not just casting to `List<int>` instead of `List<object>` solve the problem?
Vlad
Sure, but the question asks "how to call it **if the generic type isn't known**?" (emphasis added). Hence my note that in reality he'd also have to figure out the listItemType using reflection rather than just assuming it's int.
itowlson
I tried this, but I have two problems: 1. How will I get the type of the embedded elements if the list is empty? 2. I have two ToDataTable() extension methods. How to get the one for IList<T>?
alex
Alex: From your answer, it looks like you've gone for the non-generic `IList` solution and solved the problem using overloads, so I'm guessing you no longer need answers to these two questions -- leave another comment if you do.
itowlson
@itowlson: I haven't a problem any more but I'm interested in the answers. That is the reason I haven't closed the question already. :) It's your choice.
alex
alex: I've updated the answer to answer your first question. Not sure about your second question -- what extension methods are you trying to distinguish between?
itowlson
A: 

After having trouble to get it working with the IList<T> interface I solved it using the IList interface like itowlson proposed. It's a little bit ugly because of the _T method but it works well:

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);
        }            
    }
}
alex