Why isn't .NET 4.0 covariance working in this example?
I'm getting compiler errors on these lines:
RenderLookup(Cars);
RenderLookup(Employees);
Error 2 Argument 1: cannot convert from 'Demo.MyList<Demo.Car>' to 'System.Collections.Generic.IEnumerable<Demo.KeyedBase>' Program.cs 85 26 Demo
Error 4 Argument 1: cannot convert from 'Demo.MyList<Demo.Employee>' to 'System.Collections.Generic.IEnumerable<Demo.KeyedBase>' Program.cs 86 26 Demo
What's the deal? I thought .NET 4.0 would handle this? What am I missing?
using System;
using System.Collections.Generic;
namespace Demo
{
public interface KeyedBase
{
int Key { get; }
string Description { get; }
}
public class MyList<T> : Dictionary<int, T> where T : KeyedBase
{
public void Add(T itm)
{
Add(itm.Key, itm);
}
}
public class Car : KeyedBase
{
private readonly int _ID;
private readonly string _Description;
public Car(int ID, string Description)
{
_ID = ID;
_Description = Description;
}
public int K开发者_JS百科ey
{
get { return _ID; }
}
public string Description
{
get { return _Description; }
}
}
public class Employee : KeyedBase
{
private readonly int _ID;
private readonly string _FirstName;
private readonly string _LastName;
public Employee(int ID, string FirstName, string LastName)
{
_ID = ID;
_FirstName = FirstName;
_LastName = LastName;
}
public int Key
{
get { return _ID; }
}
public string Description
{
get { return _LastName + ", " + _FirstName; }
}
}
class Program
{
private static void RenderLookup(IEnumerable<KeyedBase> Lookup)
{
Console.WriteLine("Choose:");
foreach (var itm in Lookup)
{
Console.WriteLine("{0} : {1}", itm.Key, itm.Description);
}
}
static void Main(string[] args)
{
var Cars = new MyList<Car> { new Car(1, "Subaru"), new Car(2, "Volswagen") };
var Employees = new MyList<Employee>
{
new Employee(1, "Mickey", "Mouse"),
new Employee(2, "Minnie", "Mouse")
};
RenderLookup(Cars);
RenderLookup(Employees);
}
}
}
You're trying to cast Dictionary<int, T>
-derived class into IEnumerable<T>
. You have to cast it to IEnumerable<KeyValuePair<int, T>>
or pass Values
collection from your dictionaries.
RenderLookup(Cars.Values);
RenderLookup(Employees.Values);
This is not a problem with covariance but just with type compatibility.
First of all, co-/contravariance support is opt-in. You need to add the qualifiers out
or in
to a generic type parameter in order for it to work (on an interface, not a class). So you'd have to have:
interface IMyList<(in/out) T> : ...
Secondly, you can't do this in your example because your parameter T
is both co- and contravariant. To simplify, a type parameter that is only returned by a class can be covariant (aka out
), and a type parameter that is only accepted by a class is contravariant (aka in
). Your class both accepts and returns T
.
Lastly, your real problem is that Dictionary<K,V>
isn't IEnumerable<V>
, but IEnumerable<KeyValuePair<K,V>>
. You'll need to override the GetEnumerator method on MyList like so:
public class MyList<T> : Dictionary<int, T>, IEnumerable<T> where T : KeyedBase
{
public void Add(T itm)
{
Add(itm.Key, itm);
}
public virtual IEnumerator<T> GetEnumerator()
{
return Values.GetEnumerator();
}
}
Your example should then work.
MyList<Car>
indirectly implements IEnumerable<KeyValuePair<int, Car>>
(via Dictionary<int,Car>
) and so it isn't compatible with IEnumerable<KeyedBase>
.
精彩评论