Почему этот актерский состав не работает? - PullRequest
2 голосов
/ 24 июня 2010

У меня есть следующий код:

  var commitmentItems = new List<CommitmentItem<ITransaction>>();
  commitmentItems.Add(new CapitalCallCommitmentItem());

И я получаю следующую ошибку:

Argument '1': cannot convert from 'Models.CapitalCallCommitmentItem' to
'Models.CommitmentItem<Models.ITransaction>'

Однако CapitalCallCommitmentItem наследуется от CommitmentItem<CapitalCall>, а CapitalCall реализует ITransaction. Так почему ошибка?

Вот лучший пример:

CapitalCall реализует ITransaction

            var test = new List<ITransaction>();
            test.Add(new CapitalCall());
            var test2 = new List<List<ITransaction>>();
            test.Add(new List<CapitalCall>()); // error.

Ответы [ 5 ]

6 голосов
/ 24 июня 2010

Давайте сократим эти имена.

C = CapitalCallCommentItem
D = CommitmentItem
E = CapitalCall
I = ITransaction

Итак, ваш вопрос:

interface I { }
class D<T>
{
    public M(T t) { }
}
class C : D<E> { } 
class E : I { }

И ваш вопрос «почему это незаконно?»

D<E> c = new C(); // legal
D<I> d = c; // illegal

Предположим, это было законнои вывести ошибку.У c есть метод M, который принимает E. Теперь вы говорите

class F : I { }

Предположим, что было допустимо назначить c для d.Тогда было бы также законно вызывать dM (new F ()), потому что F реализует I. Но dM - это метод, который принимает E, а не F.

Разрешение этой функции позволяет вам писать программы, которые компилируютсячисто, а затем нарушать безопасность типов во время выполнения.Язык C # был тщательно спроектирован таким образом, чтобы количество ситуаций, в которых система типов могла быть нарушена во время выполнения, было минимальным.

6 голосов
/ 24 июня 2010

Поскольку для этого необходимо, чтобы CommitmentItem<CapitalCall> был ковариантным, чтобы его можно было присвоить CommitmentItem<ITransaction>, который в настоящее время не поддерживается.

C # 4 добавлена ​​поддержка совместной и контравариантности в интерфейсах, но не для классов.

Поэтому, если вы используете C # 4 и можете использовать интерфейс, такой как ICommitmentItem<> вместо CommitmentItem<>, вы можете получить то, что хотите, используя новые функции C # 4.

2 голосов
/ 24 июня 2010

EDIT - Лучше ссылка Lucero, потому что она описывает механизм совместной и контравариантности для интерфейсов, который находится в C # 4.0. Эти ссылки с 2007 года, но я чувствую, что они по-прежнему очень освещают.

Потому что C # 3.0 не поддерживает ковариацию или контравариантность общих аргументов. (И C # 4.0 имеет ограниченную поддержку только для интерфейсов.) Смотрите здесь для объяснения ковариации и контравариантности, а также некоторое понимание мышления, которое продолжалось, когда команда C # смотрела на внедрение этих функций в C # 4.0:

http://blogs.msdn.com/b/ericlippert/archive/2007/10/16/covariance-and-contravariance-in-c-part-one.aspx

http://blogs.msdn.com/b/ericlippert/archive/2007/10/17/covariance-and-contravariance-in-c-part-two-array-covariance.aspx

http://blogs.msdn.com/b/ericlippert/archive/2007/10/19/covariance-and-contravariance-in-c-part-three-member-group-conversion-variance.aspx

http://blogs.msdn.com/b/ericlippert/archive/2007/10/22/covariance-and-contravariance-in-c-part-four-real-delegate-variance.aspx

http://blogs.msdn.com/b/ericlippert/archive/2007/10/24/covariance-and-contravariance-in-c-part-five-higher-order-functions-hurt-my-brain.aspx

На самом деле, он просто продолжает писать и писать! Вот все, что он помечает как «ковариация и контравариантность»:

http://blogs.msdn.com/b/ericlippert/archive/tags/covariance+and+contravariance/

1 голос
/ 24 июня 2010

Понимание того, почему это не работает, может быть довольно сложным, поэтому вот аналогичный пример, заменив классы в вашем коде некоторыми хорошо известными классами из фреймворка, чтобы он выполнял роль заполнителей и (надеюсь) иллюстрировал потенциальные ловушкитакой желаемый функционал:

// Note: replacing CommitmentItem<T> in your example with ICollection<T>
// and ITransaction with object.
var list = new List<ICollection<object>>();

// If the behavior you wanted were possible, then this should be possible, since:
// 1. List<string> implements ICollection<string>; and
// 2. string inherits from object.
list.Add(new List<string>());

// Now, since list is typed as List<ICollection<object>>, our innerList variable
// should be accessible as an ICollection<object>.
ICollection<object> innerList = list[0];

// But innerList is REALLY a List<string>, so although this SHOULD be
// possible based on innerList's supposed type (ICollection<object>),
// it is NOT legal due to innerList's actual type (List<string>).
// This would constitute undefined behavior.
innerList.Add(new object());
1 голос
/ 24 июня 2010

Поскольку "A является подтипом B" , не означает, что "X<A> является подтипом X<B>".

Позвольте мне привести вам пример. Предположим, что CommitmentItem<T> имеет метод Commit(T t), и рассмотрим следующую функцию:

void DoSomething(CommitmentItem<ITransaction> item) {
    item.Commit(new SomethingElseCall());
}

Это должно работать, так как SomethingElseCall является подтипом ITransaction, так же как CapitalCall.

Теперь предположим, что CommitmentItem<CapitalCall> был подтипом CommitmentItem<ITransaction>. Тогда вы можете сделать следующее:

DoSomething(new CommitmentItem<CapitalCall>());

Что будет? Вы получите ошибку типа в середине DoSomething, потому что SomethingElseCall передается там, где ожидалось CapitalCall. Таким образом, CommitmentItem<CapitalCall> является , а не подтипом CommitmentItem<ITransaction>.

В Java эту проблему можно решить с помощью ключевых слов extends и super, ср. вопрос 2575363 . К сожалению, в C # такого ключевого слова нет.

...