1

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().GetGenericArguments();

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

pm100
  • 48,078
  • 23
  • 82
  • 145
  • There is no "double" going on in the *covariance* and after I created a `list` instance your `LoadNodes` method compiles. – jbtule Sep 08 '11 at 00:40
  • 2
    Perhaps it's double _secret_ covariance. ;) – TrueWill Sep 08 '11 at 01:04
  • i say double because List is derived from IEnumerable and DeviceNode is derived from INode. If I change so only one of the types is a derived class of the declared type then all works OK – pm100 Sep 08 '11 at 16:25

3 Answers3

1

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();
    }
}
BrandonAGr
  • 5,827
  • 5
  • 47
  • 72
  • I am really confused. When I compile that code it fails Argument 1: cannot convert from 'System.Collections.Generic.List' to 'System.Collections.Generic.IEnumerable' – pm100 Sep 08 '11 at 16:31
1

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

Community
  • 1
  • 1
Philip Daubmeier
  • 14,584
  • 5
  • 41
  • 77
  • Silverlight 4. Surely this is contravar. Contravar is on input parameters, covar is on return types – pm100 Sep 08 '11 at 16:32
  • does not compile for me - SL 4 – pm100 Sep 08 '11 at 16:33
  • @pm100: You should have mentioned in the first place that you are using SL, the 'little brother' of the full framework. To your first comment: co- und contravariance have nothing to do with input parameters or return types. In this case it is covariance because you are putting something specialized into something more general (DeviceNode into INode). In other words you are upcasting all collection items. – Philip Daubmeier Sep 08 '11 at 18:47
1

This is a feature missing from SL4 (Ienumerable is not marked 'out'). Apparently fixed in SL5

pm100
  • 48,078
  • 23
  • 82
  • 145