Backdooring Generic Lists through IList
Asked Answered
B

4

8

I have a scenario where a class loads objects of one type, due do abstractions I can not use a generic class (generics tend to spread like cancer :) but I often want to work with a generic version of the objects once retrieved, which resulted in code like this (simplified):

List<SomeClass> items = Storage.LoadItems(filename).OfType<SomeClass>().ToList();

Where LoadItems returns a List<object>, then I realized, why not instead have

public void LoadItems(string filename,IList list);

Now I can do this instead

List<SomeClass> items = new  List<SomeClass>();
LoadItems(filename,items);

Which should be more efficient. It's also seems a bit more flexible since I can take an existing List and tack on new items. So my questions are, is this a common pattern or do you have a different/better way of achieving this?

I'm also a bit curious that you can do this, if you try and add a object of the wrong type you get an exception, but does that mean that generic lists also do a type check? (which seems a bit unecessary)

EDIT It might actually be a bit more elegant to modify the pattern to

public IList LoadItems(string filename,IList list=null);

that way you can use the statement fluently and if no list is passed you could simply instantiate a List<object>

Beading answered 12/11, 2010 at 13:2 Comment(3)
"if you try and add a object of the wrong type you get an exception, but does that mean that generic lists also do a type check?" Of course they do. Generics are strongly typed. Why would it be unnecessary that generics check type before loading?Essam
May I ask why abstraction should render generics unusable? Generics and abstraction should rather support each other. You can use a placeholder for the generic type like List<T> where necessary.Fortress
In some scenarios where you have lot's of factories, interfaces etc using generic versions of (your own) classes make things very hard or impossible since you upfront don't know your T, and it also requires lot's of other classes to be generic. I think Marc also can attest to the potential problems of injecting generics into your class structure :)Beading
G
3

List<T> implements IList explicitly. The implementations cast to T and call the regular (generic) methods.

Thus, type-checking only happens if you explicitly call the IList methods. For example:

void System.Collections.IList.Insert(int index, Object item) 
{
    ThrowHelper.IfNullAndNullsAreIllegalThenThrow<T>(item, ExceptionArgument.item); 

    try {
        Insert(index, (T) item); 
    }
    catch (InvalidCastException) {
        ThrowHelper.ThrowWrongValueTypeArgumentException(item, typeof(T));
    } 
}

It doesn't use the as keyword because the T might be a value type.
You can only write as T if you wrote where T : class.

Galvanism answered 12/11, 2010 at 13:4 Comment(2)
ah, that makes sense, although using "as" and an if-statement rather than boxing and try-catch in your example would probable be more performantBeading
@MattiasK: I'm well aware of that. However, it wouldn't compile. You can only use the as keyword on a generic parameter if you constrain it with where T : class. Lists can contain structs.Galvanism
B
1

Using IList is fine in most cases; and is certainly faster than using reflection or dynamic to achieve the same.

Yes it will add a type-check (by virtue of the cast/unbox), but that will not be onerous. If T is a struct then you also have some boxing/unboxing, but that too isn't as bad as people fear.

In that scenario, IList would be fine by me.

Bunko answered 12/11, 2010 at 13:8 Comment(0)
F
0

Your solution does look sound, there is nothing wrong with it.

Concerning the Add, it doesn't really do a type check. The code for the Add you refer to is this:

int System.Collections.IList.Add(Object item)
{
    try {
        Add((T) item);
    }
    catch (InvalidCastException) {
        throw ...;
    }

    return Count - 1;
}

It doesn't do a type check; it's just a try/catch.

Finer answered 12/11, 2010 at 13:8 Comment(2)
The (T)item is implicitly a type-check of sorts.Bunko
Yes and no. Of course it checks the type, but there is no check here. It's more like lets see how far we'll get. If the catch would convert it to something like -1 (result instead of exception), this would probably make me cry. With "check" I mean something that involves an if :).Finer
M
0

I like the second approach.

You can pass the IList but then check the type to see if it is a generic list and if so, get generic type and load only records of that type:

   Type itemType = typeof (object);
   if(list.GetType().GetGenericArguments().Length>0)
   {
       itemType = list.GetType().GetGenericArguments()[0];
   }

   for (int i = 0; i < recordCount; i++)
   {
      if(record.GetType().IsInstanceOfType(itemType))
   }
Millikan answered 12/11, 2010 at 13:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.