двойная контравариантность в c # 4 - PullRequest
1 голос
/ 08 сентября 2011

Ранее я спрашивал об этом как о «двойной ковариации» и понял, что у меня все с ног на голову

У меня есть интерфейс

public interface INodeProvider
{
    void LoadNodes(INode parent, Action<IEnumerable<INode>> action);
}

У меня есть класс, полученный из INode, называемый DeviceNode

У меня есть класс, который реализует интерфейс

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);
}

Это не компилируется.Но это делает

                List<INode> devices = new List<INode>();

Это также компилируется, если я

 action(devices.Cast<INode>());

И возвращаюсь к моей первоначальной декларации

Это удивило меня.Может быть, мне нужно больше читать.

Но у меня есть еще один вопрос.Вызываемый делегат (действие) действительно должен знать тип объекта в IEnumerable.т.е. я хочу сделать

Type[] typeParameters = l.GetType().GetGenericArguments();

в методе действия.Я предполагаю, что это даст мне фактический тип, используемый для создания экземпляра IEnumerable.За исключением его всегда INode, учитывая, что я могу получить для компиляции.Я был удивлен, что тот, в котором Cast <> () по-прежнему говорил INode, учитывая, что базовый тип - List {DeviceNode} (я думаю, это означает, что .Cast фактически копирует)

Примечание - я знаю, что могосмотрите объекты в коллекции, чтобы определить их тип, но это не то, что я хочу сделать

РЕДАКТИРОВАТЬ: это похоже на разницу между Silverlight 4 и «нормальным» .net 4. Если я беру один и тот же коди вставьте его в проект .net 4, он прекрасно компилируется

Ответы [ 3 ]

1 голос
/ 08 сентября 2011

На ваш первый вопрос:

Проблема в том, что у вас есть ковариация , а не контрвариантность здесь.Это невозможно для непостоянных списков, даже с C # 4.0, просто потому, что это допускает странные вещи.

Проблема заключается в этой строке:

action(devices);

Что вы делаетездесь нужно попытаться поместить List<DeviceNode> в параметр IEnumerable<INode> действия.IEnumerable<> является базой (интерфейсом) для List<>, так что, похоже, это подходит.

Однако здесь проблема не в этом, подпись действия также может быть List<INode>.Предположим на мгновение, что это будет так.Теперь ваше дело похоже на то, что вы хотите вставить List<DeviceNode> в List<INode>:

List<INode> mylist = new List<DeviceNode>();

Это не удается (ковариация).Зачем?Если бы это было разрешено, вы могли бы сделать следующее:

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 и mybaselist ссылаются / указывают на один и тот же список.Это означает, что mylist теперь будет иметь элемент , а не a DeviceNode.

(. См. Также этот вопрос по SO )

В C # 3.5 IEnumerable<T> ведет себя точно так же, как List<T> в этом случае.Как указал BrandonAGr, это изменилось с введением IEnumerable<out T> в C # 4.0.Так что это должно работать так, как вы это делали в первом примере.

На второй вопрос:

Небольшая настройка показывает, что она работает так, как вы ожидали в 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!");
}

Он выписывает 'works'.

Итак: проверьте, компилируете ли вы с C # 4.0

1 голос
/ 08 сентября 2011

Эта функция отсутствует в SL4 (Ienumerable не помечен как 'out').Видимо исправлено в SL5

1 голос
/ 08 сентября 2011

Вы используете .Net 4? Следующий пример компилируется и работает в .net 4, но не в 3.5. Поскольку в 4.0 IEnumerable определяется как IEnumerable<out T>, дополнительная информация о дисперсии описана здесь

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