IDictionary <,> контравариантность?

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

public static void DoStuffWithAnimals(IDictionary<string, Animal> animals)

В моем кодовом коде у меня уже есть объект Dictionary<string, Lion>, но я не могу передать это как аргумент метода. Итак, a IDictionary<,> не является контравариантным? Я не вижу причин, почему это не должно работать.

Единственное решение, о котором я могу думать, это:

var animals = new Dictionary<string, Animal>();

foreach(var kvp in lions) {
    animals.Add(kvp.Key, kvp.Value);
}

Нет ли способа передать этот словарь в этот метод, не создавая новый словарь тех же объектов?


EDIT:

Как и мой метод, я знаю, что единственный член, который я использую из словаря, является получателем TValue this[TKey key], который является членом IDictionary<TKey, TValue>, поэтому в этом случае я не могу использовать "более широкий" тип для параметра.

Ответ 1

Убедившись, что вы могли бы сделать что-то подобное, просто перейдите в аксессор вместо:

    public static void DoStuffWithAnimals(Func<string, Animal> getAnimal)
    {
    }

    var dicLions = new Dictionary<string, Lion>();
    DoStuffWithAnimals(s => dicLions[s]);

Очевидно, что это может быть немного просто для ваших нужд, но если вам нужно только несколько методов словаря, довольно легко получить это на месте.

Это еще один способ, который дает вам немного повторного использования кода между вашими животными:

    public class Accessor<T> : IAnimalAccessor where T : Animal
    {
        private readonly Dictionary<string, T> _dict;

        public Accessor(Dictionary<string, T> dict)
        {
            _dict = dict;
        }

        public Animal GetItem(String key)
        {
            return _dict[key];
        }
    }

    public interface IAnimalAccessor
    {
        Animal GetItem(string key);
    }

    public static void DoStuffWithAnimals(IAnimalAccessor getAnimal)
    {
    }

    var dicLions = new Dictionary<string, Lion>();
    var accessor = new Accessor<Lion>(dicLions);
    DoStuffWithAnimals(accessor);

Ответ 2

Во-первых, ковариация и контравариантность в С# применимы только к интерфейсам и делегатам.

Так что ваш вопрос действительно о IDictionary<TKey,TValue>.

При этом проще всего помнить, что интерфейс может быть только ко/противо-вариантным, если все значения параметра типа либо только передаются, либо только передаются.

Например (контравариантность):

interface IReceiver<in T> // note 'in' modifier
{
    void Add(T item);
    void Remove(T item);
}

И (ковариация):

interface IGiver<out T> // note 'out' modifier
{
    T Get(int index);
    T RemoveAt(int index);
}

В случае IDictionary<TKey,TValue>, оба параметр типа используется как в качестве in и out емкости, а это означает, что интерфейс не может быть ковариантным или контравариантным. Это инвариант.

Тем не менее, класс Dictionary<TKey,TValue> действительно реализует IEnumerable<T> который является ковариантным.

Отличная ссылка для этого:

https://docs.microsoft.com/en-us/dotnet/standard/generics/covariance-and-contravariance

Ответ 3

Предположим, что Derived является подтипом Base. Тогда Dictionary<Base> не может быть подтипом Dictionary<Derived>, потому что вы не можете поместить любой объект типа Base в Dictionary<Derived>, но Dictionary<Derived> не может быть подтипом Dictionary<Base>, потому что если вы получаете объект из Dictionary<Base>, это может быть не Derived. Следовательно, Dictionary не является ни ковариантным, ни контравариантным в его параметре типа.

Как правило, коллекции, которые вы можете написать, являются инвариантными по этой причине. Если у вас есть неизменная коллекция, то она может быть ковариантной. (Если у вас есть какая-то коллекция только для записи, это может быть контравариантным.)

РЕДАКТИРОВАТЬ: И если у вас есть "коллекция", в которую вы не могли бы получить данные и не вставлять данные, тогда она может быть одновременно и ковариационной, и контравариантной. (Это также было бы бесполезно.)

Ответ 4

Вы можете изменить public static void DoStuffWithAnimals(IDictionary<string, Animal> animals), чтобы добавить общее ограничение. Вот что работает для меня в LINQPad:

void Main()
{
    var lions = new Dictionary<string, Lion>();
    lions.Add("one", new Lion{Name="Ben"});
    AnimalManipulator.DoStuffWithAnimals(lions);
}

public class AnimalManipulator
{
    public static void DoStuffWithAnimals<T>(IDictionary<string, T> animals)
    where T : Animal
    {
        foreach (var kvp in animals)
        {
            kvp.Value.MakeNoise();
        }
    }
}

public class Animal
{
    public string Name {get;set;}
    public virtual string MakeNoise()
    {
        return "?";
    }
}

public class Lion : Animal
{
    public override string MakeNoise()
    {
        return "Roar";
    }
}

Ответ 5

  • Я считаю, что то, что вы хотите, называется ковариацией, а не контравариантностью.
  • Классы в .Net не могут быть ковариантными или контравариантными, могут только интерфейсы и делегаты.
  • IDictionary<TKey, TValue> не может быть ковариантным, потому что это позволит вам сделать что-то вроде:

    IDictionary<string, Sheep> sheep = new Dictionary<string, Sheep>();
    IDictionary<string, Animal> animals = sheep;
    animals.Add("another innocent sheep", new Wolf());
    
  • В .Net 4.5 есть IReadOnlyDictionary<TKey, TValue>. На первый взгляд это может быть ковариантным (новый новый интерфейс IReadOnlyList<T> является ковариантным). К сожалению, это не так, потому что он также реализует IEnumerable<KeyValuePair<TKey, TValue>>, а KeyValuePair<TKey, TValue> не ковариант.

  • Чтобы обойти это, вы можете сделать свой метод общим с ограничением параметра типа:

    void DoStuffWithAnimals<T>(IDictionary<string, T> animals) where T : Animal
    

Ответ 6

Если интерфейс словаря был доступен только для чтения (позволяя вам только считывать пары ключ-значение и даже не позволять возможность вытягивать значение по его ключу), он может быть отмечен с помощью out generic модификатор параметра. Если интерфейс словаря был только для записи (позволяющий вставлять значения, но не извлекать их или перебирать по ним), он может быть помечен модификатором параметров in.

Итак, вы можете создать интерфейс самостоятельно и расширить класс Dictionary, чтобы получить необходимую функциональность.