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?
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With