7

I want to create a generic List<> whose type is declared at runtime.

I can do the following, but since it's dynamic, I suspect there is a speed penalty. I'm writing a wrapper to an exotic database, so speed is critical.

List<dynamic> gdb = new List<dynamic>()

I read this post in dynamic generic types, but can't get it to work. Specifically, the object is not appearing as a List and hence has no add method.

    Type ac;

    switch (trail[dataPos].Type)
    {
        case GlobalsSubscriptTypes.Int32:
            ac = typeof(System.Int32);

            break;
        case GlobalsSubscriptTypes.Int64:
            ac = typeof(System.Int64);

            break;

        default:
            ac = typeof(System.String);

            break;
    }

    var genericListType = typeof(List<>);
    var specificListType = genericListType.MakeGenericType(ac);
    var gdb = Activator.CreateInstance(specificListType);

How do I get gdb to appear as one of the following:

List<System.Int32>
List<System.Int64>
List<System.String>
Community
  • 1
  • 1
IamIC
  • 17,747
  • 20
  • 91
  • 154
  • 1
    It might be easier to just use a `List` in that case. If the type isn't known at compile time then the compile time checking that generics give you won't be of any help to you. – Servy Oct 24 '12 at 16:02
  • Not a bad idea, but I want the list to be typed since it will be part of a query. – IamIC Oct 24 '12 at 16:03
  • @IanC Why is it that your database objects aren't known at compile time. Really, that seems to be the underlying problem here. And as I said before, having the `List` be typed won't do you any good if you don't know the type at compile time. You won't be able to cast the result to a typed list, you won't know the types that you can or can't add at compile time, and you won't know anything about what the objects in the list can do, so you can't do anything useful with the items when you take them out. – Servy Oct 24 '12 at 16:04
  • @lukas That's in the question already as what he's currently doing. – Servy Oct 24 '12 at 16:04
  • The types are known at compile time. I am writing a query wrapper, so I need to able to set the results type via code. – IamIC Oct 24 '12 at 16:10
  • @lukas List is the exact same type as List at runtime it just changes how the compiler deals with the values when they are returned out of the list. – jbtule Oct 24 '12 at 16:39
  • @jbtule You say " it just changes how the compiler deals with the values when they are returned out of the list" -- which is more efficient? – IamIC Oct 24 '12 at 16:47
  • 2
    @IanC so say you have a `List d` and `List o`. Then proceed to assign `int i = d[0];` and `int j = (int)o[0];` if the lists contain `int`s then `j` assignment is certainly faster, if the lists contain something implicitly convertible to ints 'j' assignment would not work at all and 'i' would. – jbtule Oct 24 '12 at 17:14
  • 1
    @jbtule I tested this yesterday, and yes, object wins. – IamIC Oct 25 '12 at 18:22

4 Answers4

5

Once you've used Activator.CreateInstance, you can cast the result to the appropriate type. You could use IList for example:

var gdb = (IList)Activator.CreateInstance(specificListType);
gdb.Add(1);

Note that the above throws an ArgumentException if the type you're adding does not match the generic type.

McGarnagle
  • 101,349
  • 31
  • 229
  • 260
  • This is the answer I was looking for. I was casting in the wrong place. – IamIC Oct 24 '12 at 16:05
  • If you're going to just use the non-generic interface you may as well just use a `List` and save yourself the need to use reflection to create the List. – Servy Oct 24 '12 at 16:07
  • 1
    @Servy that will require every int and long to be boxed and unboxed, which is a significant performance hit. – IamIC Oct 24 '12 at 16:08
  • 2
    @IanC Using `IList` will still cause the ints to be boxed and unboxed when you add in this method... – Reed Copsey Oct 24 '12 at 16:09
  • 1
    @IanC That's going to be happening anyway here, since you're using the non-generic methods. The add method called here is `public void Add(object item)`. – Servy Oct 24 '12 at 16:09
  • Ok, interesting. Your idea will work. The only advantage to the way I'm doing it seems to be that the list is typed, which means the client code doesn't need to perform casts. – IamIC Oct 24 '12 at 16:16
3

Oh, gdb is of the correct type. Since you determine the type at runtime, the compiler does not know it. Hence, it is statically typed as object and does not show an Add method. You have a few options to fix that:

  1. Cast it to the correct type:

    switch (trail[dataPos].Type) 
    { 
        case GlobalsSubscriptTypes.Int32: 
            ((List<int>) gdb).Add(...); 
            break; 
        ...
        default: 
            ((List<String>) gdb).Add(...); 
            break; 
    } 
    
  2. Cast to a common supertype:

    ((System.Collections.IList) gdb).Add(...);
    
  3. Use dynamic invocation:

    dynamic gdb = Activator.CreateInstance(specificListType);
    gdb.Add(...);
    
Heinzi
  • 167,459
  • 57
  • 363
  • 519
1

In your case, gdb will always be a System.Object, as CreateInstance returns objects of any type.

You have a few options here - you could cast it as IList (non-generic), as you know that List<T> implements this interface, and use IList.Add.

Alternatively, you could just declare it dynamic, and use dynamic binding:

dynamic gdb = Activator.CreateInstance(specificListType);

This will let you write your code as if it's a List<T> of the appropriate type, and the calls will be bound at runtime via dynamic binding.

Reed Copsey
  • 554,122
  • 78
  • 1,158
  • 1,373
1

List<dynamic> compiles the exact same type as List<object> no speed penalties over a typed List<int>, for example, beyond boxing value types. However if you are going to cast to IList you are still going to have the boxing penalty FYI.

You could run into trouble is calling Activator (a reflection method) as it is likely to be dramatically slower that calling a constructor directly.

Does any of this matter? You won't know unless you actually run profile because it will always depends on your actual usage.

My best guest what you really want to do is:

IList gdb;

switch (trail[dataPos].Type)
{
    case GlobalsSubscriptTypes.Int32:
        gdb = new List<int>();
        break;
    case GlobalsSubscriptTypes.Int64:
        gdb = new List<long>();
        break;
    default:
        gdb = new List<string>();
        break;
}

Also if you really need to do operations without boxing make a generic helper method to do all your work:

switch (trail[dataPos].Type)
{
    case GlobalsSubscriptTypes.Int32:
        return Helper<int>(trail[dataPos]);
    case GlobalsSubscriptTypes.Int64:
        return Helper<long>(trail[dataPos]);
    default:
        return Helper<string>(trail[dataPos]);
}
jbtule
  • 31,383
  • 12
  • 95
  • 128
  • Thanks for your clear explanation. I like the Helper idea (although I haven't thought much about its code). As it turns out, due to polymorphism I have no option but to use dynamic in the class that gets the value from the database. So I am assuming I should simply go with List. – IamIC Oct 24 '12 at 16:39
  • 1
    @IanC I'd go with whatever will produce the simplest code, and then worry about speed later. – jbtule Oct 24 '12 at 16:43