Не удается преобразовать словарь в IDictionary в вызове метода


Я ни за что на свете не могу этого понять. Допустим, у меня есть следующие два объекта словаря:

// Assume "query" is a LINQ queryable.
Dictionary<string, int> d1 = query.ToDictionary(k => k.Key, v => v.Value);
Dictionary<string, int>  d1 = query.ToDictionary(k => k.Key, v => v.Value);

Следующее утверждение приводит к ошибке времени компиляции, что неявное преобразование между словарем и IDictionary невозможно:

// Compile time error!
Tuple<IDictionary<string, int>, IDictionary<string, int>> = Tuple.Create(d1, d2);

Я должен выполнить преобразование явно:

Tuple<IDictionary<string, int>, IDictionary<string, int>> = Tuple.Create(d1 as IDictionary<string, int>, d2 as IDictionary<string, int>);

Я не понимаю, почему компилятор не может вычислить операцию ковариации-словарь реализует IDictionary - тем более, что что-то вроде этого будет курсовая работа, как мы все знаем:

IDictionary<string, int> d3 = d1;
Я уверен, что есть веская причина для такого поведения, и мне любопытно, что это такое.

Обновление 1: Просто чтобы уточнить, мне интересно поведение, а не то, как решить проблему. Я знаю о различных решениях :)

Обновление 2: Спасибо всем за отличные ответы. Я не знал, что Tuple инвариантно, и теперь знаю.

5 4

5 ответов:

В основном проблема заключается в том, что семейство Tuple не является ковариантным в своих аргументах типа. Этого не может быть, потому что это класс. Интерфейс или версия делегата может быть создана , которая была бы ковариантной, однако, поскольку нет никаких членов, принимающих параметры типа во входных позициях.

Это проще всего увидеть с помощью Tuple<T1>:

Tuple<string> stringTuple = Tuple.Create("Foo");
Tuple<object> objectTuple = stringTuple;

Этот вызов:

Tuple.Create(d1, d2);

... выводит оба аргумента типа как Dictionary<string, int>, поэтому вы пытаетесь преобразовать из Tuple<Dictionary<string, int>, Dictionary<string, int>> к Tuple<IDictionary<string, int>, IDictionary<string, int>>, который не работает.

Версия с as изменяет типы аргументов так, что вывод типа дает желаемые аргументы типа - но было бы проще просто написать аргументы типа напрямую и полностью избежать вывода, Как в ответе Себастьяна:

Tuple.Create<IDictionary<string, int>, IDictionary<string, int>>(d1, d2)

Если вы используете var в этот момент, это не так плохо:

var x = Tuple.Create<IDictionary<string, int>, IDictionary<string, int>>(d1, d2);

Тип x теперь будет Tuple<IDictionary<string, int>, IDictionary<string, int>>, который вы хотите.

EDIT: как отмечено в комментариях, вы также можете просто используйте конструктор в этой точке:

var x = new Tuple<IDictionary<string, int>, IDictionary<string, int>>(d1, d2);

Вы пытаетесь назначить...

Tuple<Dictionary<string, int>, Dictionary<string, int>>

... к ...

Tuple<IDictionary<string, int>, IDictionary<string, int>>

...но Tuple<T1, T2> является классом и поэтому инвариант.

Параметры типа Для Кортежа.Create выводятся из типов, и, таким образом, правая сторона является кортежем из двух словарей, и это другой тип, чем Кортеж IDictionarys - если вы явно добавляете параметры типа в кортеж.Create to be of type IDictionary все должно работать, как ожидалось, потому что это нормально для параметров метода, чтобы быть более определенного типа.

 Tuple.Create<IDictionary<string, int>,IDictionary<string, int>>(d1,d2)

Нет никакого преобразования между a Tuple<Dictionary<string, int>, Dictionary<string, int>> и A Tuple<IDictionary<string, int>, IDictionary<string, int>>, так как классы в C# (и, следовательно, класс Tuple<T1, T2>) не поддерживают ковариацию.

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

Попробуйте изменить тип переменной с Dictionary<string, int> на IDictionary<string, int>.

IDictionary<string, int> d1 = query.ToDictionary(k => k.Key, v => v.Value);
IDictionary<string, int>  d1 = query.ToDictionary(k => k.Key, v => v.Value);