开发者

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

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜