IDictionary的<,>逆变?

时间:2012-10-11 14:06:06

标签: c# .net

我在外部类中有以下方法

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

在我的调用代码中,我已经有了一个Dictionary<string, Lion>对象,但我无法将此作为此方法的参数传递。那么IDictionary<,>不是逆变的吗?我看不出为什么这不起作用的原因。

我能想到的唯一解决方案是:

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

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

是否无法将此字典传递给此方法,而无需创建相同对象的新字典?


修改

因为这是我的方法,我知道我在字典中使用的唯一成员是TValue this[TKey key]的getter,它是IDictionary<TKey, TValue>的成员,所以在这种情况下,我是无法为参数使用“更宽”的类型。

6 个答案:

答案 0 :(得分:8)

首先,C#中的协方差和逆变仅适用于接口和代理。

所以你的问题实际上是关于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>的情况下,两个类型参数都用于inout容量,这意味着接口不能是协变的或逆变的。它是不变的。

但是,班级Dictionary<TKey,TValue>确实实现了IEnumerable<T>,这是协变的。

对此的一个很好的参考是:

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

答案 1 :(得分:7)

解决方案明智的做法是你可以这样做,只需传入一个存取器:

    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 :(得分:4)

假设DerivedBase的子类型。然后,Dictionary<Base>不能是Dictionary<Derived>的子类型,因为您无法将Base类型的任何对象放入Dictionary<Derived>,但Dictionary<Derived>可以t是Dictionary<Base>的子类型,因为如果您从Dictionary<Base>中获取对象,则可能不是Derived。因此,Dictionary在其类型参数中既不是共变量也不是逆变量。

通常,由于这个原因,您可以写入的集合是不变的。如果你有一个不可变的集合,那么它可能是协变的。 (如果你有某种只写的集合,它可能是逆变的。)

编辑:如果你有一个“集合”,你既不能从中获取数据也不能将数据输入,那么它可能既有共变,也有逆变。 (它也可能没用。)

答案 3 :(得分:2)

您可以修改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";
    }
}

答案 4 :(得分:2)

  1. 我相信你想要的是协方差,而不是逆变。
  2. .Net中的类不能是共变或逆变的,只有接口和代理才可以。
  3. IDictionary<TKey, TValue>不能协变,因为这可以让你做类似的事情:

    IDictionary<string, Sheep> sheep = new Dictionary<string, Sheep>();
    IDictionary<string, Animal> animals = sheep;
    animals.Add("another innocent sheep", new Wolf());
    
  4. .Net 4.5中有IReadOnlyDictionary<TKey, TValue>。乍一看它可能是协变的(另一个新界面,IReadOnlyList<T>是协变的)。不幸的是,它不是,因为它也实现IEnumerable<KeyValuePair<TKey, TValue>>KeyValuePair<TKey, TValue>不是协变的。

  5. 要解决此问题,您可以使用类型参数的约束使您的方法通用:

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

答案 5 :(得分:0)

如果字典的界面是只读的(允许您只读取键值对,甚至不能通过其键提取值),则可以使用{{1}标记通用参数修饰符。如果字典的接口是只写的(允许您插入值,但不能检索它们或迭代它们),则可以使用out泛型参数修饰符标记它。

因此,您可以自己创建界面并扩展Dictionary类,以获得所需的功能。