double contravariance in c# 4
I previously asked this as 'double covariance' and realized that I had things upside down
I have an interface
public interface INodeProvider
{
void LoadNodes(INode parent, Action<IEnumerable<INode>> action);
}
I have a class derived from INode, called DeviceNode
I have a class that implements the interface
public override void LoadNodes(INode parent, Action<IEnumerable<INode>> action)
{
List<DeviceNode> devices = new List<DeviceNode>();
foreach (var dev in list)
{
devices.Add(new DeviceNode(this, dev));
}
action(devices);
}
This does not compile. But this does
List<INode> devices = new List<INode>();
It also compiles if I do
action(devices.Cast<INode>());
And revert back to my original declaration
This surprised me. Maybe I need to read more.
But I have another question. The delegate being invoked (action), really needs to know the type of the object in the IEnumerable. ie I want to do
Type[] typeParameters = l.GetType().GetGenericArgumen开发者_Python百科ts();
in the action method. I assume this will give me the actual type used to instantiate the IEnumerable. Except its always INode given what I can get to compile. I was surprised that the one with Cast<>() on it still said INode given that the underlying type is List{DeviceNode} (I guess this means that .Cast actually copies)
Note - I know I could inspect the objects in the collection to determine their type, but that's not what I want to do
EDIT: This seems to be a difference between Silverlight 4 and 'normal' .net 4. If I take the same code and paste it into a .net 4 project it compiles fine
Are you using .Net 4? The following example compiles and works in .net 4 but not 3.5. Because in 4.0 IEnumerable is defined as IEnumerable<out T>
, more information on Variance is described here
public interface INode
{
string Name { get; set;}
}
class DeviceNode : INode
{
public string Name { get; set; }
public string SomethingElse { get; set; }
}
public interface INodeProvider
{
void LoadNodes(INode parent, Action<IEnumerable<INode>> action);
}
class NodeProvider : INodeProvider
{
public void LoadNodes(INode parent, Action<IEnumerable<INode>> action)
{
List<DeviceNode> devices = new List<DeviceNode>() { new DeviceNode(){ Name="DeviceNode1", SomethingElse="OtherProperty" } };
action(devices);
}
}
class Program
{
static void Main(string[] args)
{
var provider = new NodeProvider();
provider.LoadNodes(null, (list) => Console.WriteLine(string.Join(", ", list.Select(node => node.Name).ToArray())));
Console.ReadLine();
}
}
To your first question:
The problem is that you have covariance, not contravariance here. This is not possible for mutable lists, even with C# 4.0, simply because it would allow for strange things to happen.
The problem lies in this line:
action(devices);
What you are doing here is to try to put the List<DeviceNode>
into the IEnumerable<INode>
parameter of the action. IEnumerable<>
is a base (interface) for List<>
, so this seems to fit.
However, this is not the problem here, the signature of the action could as well be List<INode>
. Lets assume for a moment this would be the case. Now your case is like you want to stick a List<DeviceNode>
into a List<INode>
:
List<INode> mylist = new List<DeviceNode>();
This fails (covariance). Why? If it was allowed, you could make the following:
List<DeviceNode> mylist = new List<DeviceNode>();
List<INode> mybaselist;
// assume this would be legal
mybaselist = (List<INode>)mylist;
// here comes the evil part:
// OtherSpecializedNode inherits from INode but not from DeviceNode
mybaselist.Add(new OtherSpecializedNode());
mylist and mybaselist are referencing/pointing to the same list. This means mylist
would now have an element that is not a DeviceNode
.
(See also this question on SO)
In C# 3.5 IEnumerable<T>
behaves exactly like List<T>
in this case. Like BrandonAGr pointed out, this changed with the introduction of IEnumerable<out T>
in C# 4.0. So this should work like you have done in your first example.
To the second question:
A small setup shows, that it works like you expected in C# 4.0:
class Foo { }
class Bar : Foo { }
static void Main(string[] args)
{
DoSomething(new List<Bar>());
}
static void DoSomething(IEnumerable<Foo> lotOfFoos)
{
Type t = lotOfFoos.GetType().GetGenericArguments()[0];
if (t.Name == "Bar")
Console.WriteLine("works!");
}
It writes out 'works'.
So: check if you're compiling with C# 4.0
This is a feature missing from SL4 (Ienumerable is not marked 'out'). Apparently fixed in SL5
精彩评论