Can use IList but not List in generic method
Asked Answered
T

1

7

I'm trying to create a method which returns a list of whichever type the user wants. To do this I'm using generics, which I'm not too familiar with so this question may be obvious. The problem is that this code doesn't work and throws the error message Cannot convert type Systems.Collections.Generic.List<CatalogueLibrary.Categories.Brand> to Systems.Collection.Generic.List<T>

private List<T> ConvertToList<T>(Category cat)
{            
     switch (cat)
     {
         case Category.Brands:
             return (List<T>)collection.Brands.ToList<Brand>();

     }
    ...
}

But if I use IList instead, there are no errors.

private IList<T> ConvertToList<T>(Category cat)
{            
     switch (cat)
     {
         case Category.Brands:
             return (IList<T>)collection.Brands.ToList<Brand>();

     }
     ...
} 

Why can I use IList but not List in this case? collection.Brands returns a BrandCollection type from a third party library so I don't know how that's created. Could it be that BrandCollection may derive from IList (just guessing that it does) and so it can be converted to it but not to a normal List?

Timms answered 24/3, 2013 at 20:13 Comment(7)
You should put type constraints on there mate! You can't just go around casting stuff willy-nilly.Felting
Are you sure your second example is not return (IList<T>)collection.Brands.ToList<T>(); ?Langmuir
I was going to look into after this worked. I'm a bit new to generics so figured I'd leave that till later in case it broke something else :D Would the constraint be where T : Brand (and the other categories)?Timms
I feel like your generic usage here is not correct.Mordacious
@Langmuir - I tried using ToList<T> but I get Instance argument: cannot convert from '..BrandCollection' to 'System.Collections.Generic.IEnumerable<T>'Timms
@Mordacious - That may well be the case :). I'm not too familiar with them. I'd like the user to be able to call the method and retrieve a list of the type they inputted e.g. ConvertToList<Brand>(Cat.Brand) and return a List<Brand>.Timms
If BrandCollection implements non-generic IEnumerable, you should be able to use Cast<T>().ToList()Langmuir
D
9

Since there are no constraints on T, it can only be converted to object at compile time. Casts to interface types aren't checked by the compiler since there could theoretically be a new class created which implements IList<object> and inherits List<Brand>. However, the cast to List<T> will fail since it is known that there cannot be a class created which inherits both List<object> and List<Brand>. However, in your case, you know what the type T is through your switch statement and wish to force the cast. To do this, cast through object first as follows:

private List<T> ConvertToList<T>(Category cat)
{            
    switch (cat)
    {
        case Category.Brands:
            return (List<T>)(object)collection.Brands.ToList<Brand>();
    }
}

The bigger design problem here, though, is that generics are not the best choice when you have a discrete list of known types for T. Generics are better when T can either be anything, or be constrained to a base type or interface. Here, you'd be better off just writing a separate method for each branch of the switch statement:

private List<Brand> ConvertToBrandList()
{
    return collection.Brands.ToList<Brand>();
}

Without this, you have very little type safety. What if someone calls your method with ConvertToList<int>(Category.Brands)?

Dibromide answered 24/3, 2013 at 20:35 Comment(2)
Thanks for the explanation. Although the object method worked, I'll go for the individual method route. I wouldn't be able to constrain to Brand as I just found out that it's a sealed class.Timms
+1. I think the real reason is due to C# reference 6.2.4 : "The explicit reference conversions are ... From any class-type S to any interface-type T, provided S is not sealed and provided S does not implement T."Menis

© 2022 - 2024 — McMap. All rights reserved.