Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

List<IJob>.AddRange(List<Job>) doesn't work

Tags:

c#

generics

I discovered that a list of concrete objects cannot be added to a list of interface object.

public static void AddJob(List<IJob> masterJobs, List<Job> jobs)
{
    masterJobs.AddRange(jobs);  //fail to compile
}

Instead, one needs to use the following code:

public static void AddJob(List<IJob> masterJobs, List<Job> jobs)
{
    masterJobs.AddRange(jobs.Cast<IJob>());  
}

What is the rational behind this?

like image 968
Graviton Avatar asked Jul 27 '10 11:07

Graviton


2 Answers

Lasse is right about why this won't work in C# 3 - there is no conversion from List<IJob> to List<Job>.

In C# 4 it will work, not because of the list being covariant, but because IEnumerable<T> is covariant. So in other words, the code would effectively be:

public static void AddJob(List<IJob> masterJobs, List<Job> jobs)
{
    IEnumerable<IJob> jobsTmp = jobs; // This is the covariance working
    masterJobs.AddRange(jobs); // This is now fine
}

jobs implements IEnumerable<Job>, so there's a reference conversion to IEnumerable<IJob> through covariance, so it all works fine. The call to Cast<T> is effectively doing a similar job in your C# 3 workaround - you're using it to convert to an IEnumerable<IJob>.

If you want to more about generic variance, there's a video of my NDC 2010 talk available, or read Eric Lippert's series of blog posts on it.

like image 175
Jon Skeet Avatar answered Oct 22 '22 05:10

Jon Skeet


The reason is that a List<IJob> is not a List<Job>, although a Job implements IJob.

This is co- or contra-variance (I never remember which is which.)

The thinking goes something like this:

The compiler cannot guarantee that AddRange only reads things out of the parameter it is given, and thus it cannot guarantee that this is safe, and thus it fails to compile.

For instance, for all the compiler knows, AddRange could add another object into the jobs parameter, that implements IJob (because AddRange expects IJob collections), but isn't Job, which is what jobs expect, and thus that would not be safe.

In C# 4.0, there is some support for handling this, but I'm not sure it would handle your particular case since the support has to be specified on the interface-level, and not at the method-level.

In other words, you would have to specify on the interface type that everything that relates to T is only going into the collection, never out of it, and then the compiler would allow you do this. However, a collection you cannot read from would be pretty pointless.

like image 23
Lasse V. Karlsen Avatar answered Oct 22 '22 05:10

Lasse V. Karlsen