Morten Christiansen
Morten Christiansen

Reputation: 19580

How to convert a type to a generic version given its type?

I'm having a spot of trouble with generics in C#. I have to store a number of generic objects together but their type parameter differs so I have made a non-generic interface which they implement. What I'm looking for is a way to convert back to the generic version, given a type object. I know I can do it with reflection but I was wondering if there was a better/more elegant solution.

The following code illustrates the problem:

interface ITable
{
   public Type Type { get; }
}

class Table<T> : ITable
{
   public Type Type { get{ return typeof(T); } }
}

class Program
{
   static void Main(string[] args)
   {
      var tables = new Dictionary<string, ITable>();
      ... //insert tables
      DoStuffWithTable(tables["my table"]); //This doesn't work
   }

   public static void DoStuffWithTable<T>(Table<T> table)
   {
      ...//Some work
   }
}

Is there a clean way for me to invoke the generic DoStuffWithTable method based on the instance of the Type object I can get from its interface method?

Upvotes: 1

Views: 682

Answers (4)

Skizz
Skizz

Reputation: 71090

There is a misunderstanding here between generics and polymorphism. Generally, generics deal with things of a single type where the type is defined at compile time*, whereas polymorphism is about things of different types that exhibit common functionality defined as an interface or base type.

You seem to be trying to create a polymorphic type (things of different type that exhibit the same behaviour) where each polymorphic instance is defined by a generic type.

So, to update your code:

interface ITable
{
   void SomeCommonFunction ();
}

class Table<T> : ITable
{
   void SomeCommonFunction () { do something - T is known at compile time! }
}

class Program
{
   static void Main(string[] args)
   {
      var tables = new Dictionary<string, ITable>();
      ... //insert tables
      tables["my table"].SomeCommonFunction ();
   }
}

Now, if you want to do different things in SomeCommonFunction that is dependant on the type T, then you want to have specific instantiations of the Table type. C# doesn't allow for specialisations of generic type in the way that C++ can with its templates so you'll have to do:

class TableOfInt : ITable
{
   void SomeCommonFunction () { do something different! }
}

* You can define the type at run time in C# but that's getting into crazy reflection territory.

Upvotes: 0

Marc Gravell
Marc Gravell

Reputation: 1063338

If you are starting from a non-generic type (ITable), then the only way to do this is via reflection (MakeGenericMethod). It isn't very pretty or especially fast, but it works...

public static void DoStuffWithUntypedTable(ITable table)
{
    typeof(Program).GetMethod("DoStuffWithTable")
        .MakeGenericMethod(table.Type)
        .Invoke(null, new object[] { table });
}

As an aside - note that there is a bit of risk in assuming that an ITable is actually a Table<T> - you should probably verify that, and maybe also use an interface (ITable<T>).


Edit: if it really must be a Table<T>, then you can enforce this (including subclass support, such as FooTable : Table<Foo> as:

public static void DoStuffWithUntypedTable(object table)
{
    Type type = table.GetType();
    while (type != typeof(object))
    {
        if (type.IsGenericType && type.GetGenericTypeDefinition()
              == typeof(Table<>))
        {
            typeof(Program).GetMethod("DoStuffWithTable")
                .MakeGenericMethod(type.GetGenericArguments()[0])
                .Invoke(null, new object[] { table });
            return;
        }
        type = type.BaseType;
    }
    throw new ArgumentException("Not a Table<T> or subclass");
}

Upvotes: 2

Jon Skeet
Jon Skeet

Reputation: 1502046

The problem is that you don't know the type at compile-time - which is what generics is tailored for.

To call a generic method where you only know the type argument at execution time, you basically need reflection - get the generic method, call MakeGenericMethod and then invoke the returned method.

Upvotes: 1

Stefan Steinegger
Stefan Steinegger

Reputation: 64628

You need to cast, you actually need to know the actual type, unless it doesn't make sense.

DoStuffWithTable<MyType>((Table<MyType>)tables["my table"]);

You should consider to make the method not generic if you want to call it without knowing the actual type.

Upvotes: 0

Related Questions