Контравариантность делегата, приводящая к ошибке «Невозможно преобразовать из ... в ...» - PullRequest
1 голос
/ 29 апреля 2019

Чтобы упростить, допустим, у меня есть родительский и дочерний класс:

public class MyParent { }
public class MyChild : MyParent { }

И эти две функции с некоторым кодом:

public void DoSomethingBy_MyChild(MyChild myChild) { //code }
public void DoSomethingBy_MyParent(MyParent myParent) { //code }

Но когда я пытаюсь выполнить этот модульный тест делегата Action<MyChild> с DoSomethingBy_MyParent с параметром MyParent, компилятор говорит:

Ошибка CS1503 Аргумент 1: невозможно преобразовать из «MyParent» в «MyChild».

public void UnitTest()
{            
    Action<MyChild> processor;
    processor = DoSomethingBy_MyChild;
    processor(new MyChild());             //OK

    processor = DoSomethingBy_MyParent; 
    processor(new MyChild());             //OK

    processor = DoSomethingBy_MyParent;
    processor(new MyParent());            //Error
}

Ответы [ 2 ]

3 голосов
/ 29 апреля 2019

С Использование дисперсии в делегатах (C #) :

Когда вы назначаете делегату метод, ковариация и контравариация обеспечивают гибкость при сопоставлении типа делегата с сигнатурой метода. Ковариация позволяет методу иметь возвращаемый тип, который является более производным, чем тот, который определен в делегате. Контравариантность разрешает метод с типами параметров, которые являются менее производными, чем типы делегатов.


Можно присвоить DoSomethingBy_MyParent processor ( контравариантное назначение, поскольку MyParent менее производно, чем MyChild), поскольку все, что является MyChild, по определению также MyParent

Action<MyChild> processor;
processor = DoSomethingBy_MyParent;

Однако то, что происходит, когда вы пытаетесь передать MyParent в processor, эффективно

Action<MyChild> processor;
processor(new MyParent());           

Это нехорошо, потому что processor требует, чтобы в него было передано MyChild - его нельзя назвать контравариантно. Неважно, что вы присвоили ему DoSomethingBy_MyParent - processor объявлен как Action<MyChild>, поэтому должен получить экземпляр MyChild или более производный тип.


Другими словами, у вас есть

public void DoSomethingBy_MyChild(MyChild myChild) { //code }

и вы не ожидаете, что сможете назвать его так:

DoSomethingBy_MyChild(new Parent());

потому что вызовы методов работают ковариантно (вы можете передать экземпляр более производного типа), а не контравариантно (вы не можете передать экземпляр менее производного типа).

2 голосов
/ 29 апреля 2019

Это может помочь Делегаты действий, дженерики, ковариация и контравариантность

В принципе, все хорошо.Action<T> является контравариантным, поэтому вы можете назначить DoSomethingBy_MyParent на Action<MyChild> processor.Это противоречие.

Но поскольку processor имеет тип Action<MyChild>, вы не можете вызывать его с MyParent instance.

...