views:

148

answers:

5

I have the following extension method to add the elements in one collection to another collection:

public static void AddRange<T>(this ICollection<T> collection, IEnumerable<T> list)
{
    foreach (var item in list)
    {             
        collection.Add(item);
    }
}

This works fine if the IEnumerable list is the same type as the ICollection I'm trying to add it to. However, if I have something like this:

var animals = new List<Animal>();
var dogs = new List<Dog>(); // dog is a subclass of animal
animals.AddRange(dogs); // this line has a compiler error, it can't infer the type

How do I modify my extension method to be able to do something like this, if the type of the IEnumerable is a subclass (or implements the interface) of the T type?

A: 

public static void AddRange(this ICollection collection, IEnumerable list) where TB : TA

(incorporated Craig Walker's comment)

peterchen
This won't work without a "where TB: TA" clause at the end of the method.
Craig Walker
Thanks, Craig. (I knew I should have tried it)
peterchen
That's what "edit" is all about :-)
Craig Walker
+2  A: 

You can do this:

public static void AddRange<T,T2>(this ICollection<T> collection, IEnumerable<T2> list) where T2: T {
foreach (var item in list) {                     
 collection.Add(item);    
} }
Richard Hein
+3  A: 

This method will give you what you want:

public static void AddRange<A,B>(
  this ICollection<A> collection, 
  IEnumerable<B> list)
    where B: A
{
    foreach (var item in list)
    {
        collection.Add(item);
    }
}

A couple of notes:

  1. The only way that this can work is if you use a type for B that is derived from A. For example, Dog is a subclass of Animal, so AddRange will work. This is enforced by the "where B: A" clause. Without this, the ICollection.Add() call will fail due to an incompatible type for B being passed into the ICollection expecting an A.
  2. There's not a lot of need to restrict the type of A to anything in the Animal type hierarchy; the extension method could be used anywhere you have one type deriving from another.
  3. This isn't really a problem of the compiler not being able to infer the types. Even if you explicitly passed the types for A and B everywhere, you'll still get a compiler error.
Craig Walker
Thanks for the code, it worked fine! At first I thought it meant that AddRange() is expecting a key-value pair, but I'm thinking that it can tell the difference based on the parameters. And right, it's not a problem with type inference, it's a problem with type casting.
Daniel T.
A: 

I would use constraint to restrict the type.

    public static void AddRange<T, TK>(this ICollection<T> collection, IEnumerable<TK> list) where TK : T
    {
        foreach (var item in list)
        {
            collection.Add(item);
        }
    }
mqbt
+1  A: 

wait for C# 4.0 or use the Linq extension method Cast:

List<Animal> animals = new List<Animal>();
List<Dog> dogs = new List<Dog>();
animals.AddRange(dogs.Cast<Animal>());
Marc Wittke