19

我在外部类中有以下方法

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

在我的调用代码中,我已经有一个Dictionary<string, Lion>对象,但我不能将其作为此方法的参数传入。所以 aIDictionary<,>不是逆变的?我看不出这不应该起作用的任何理由。

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

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

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

有没有办法将这个字典传递给这个方法,而不必创建一个相同对象的新字典?


编辑:

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

4

6 回答 6

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>,两个类型参数都在 aninoutcapacity 中使用,这意味着接口不能是协变或逆变的。它是不变的。

但是,该类Dictionary<TKey,TValue>确实实现IEnumerable<T>了协变。

一个很好的参考是:

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

于 2012-10-11T14:25:00.560 回答
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);
于 2012-10-11T14:17:32.650 回答
4

假设它Derived是 的子类型Base。然后,Dictionary<Base>不能是的子类型,Dictionary<Derived>因为您不能将任何类型的对象Base放入 aDictionary<Derived>中,但Dictionary<Derived>也不能是的子类型,Dictionary<Base>因为如果您从 a 中取出对象Dictionary<Base>,它可能不是 a Derived。因此,Dictionary它的类型参数既不是协变的,也不是逆变的。

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

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

于 2012-10-11T14:10:07.987 回答
3

您可以修改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";
    }
}
于 2012-10-11T14:21:15.040 回答
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. IReadOnlyDictionary<TKey, TValue>在.Net 4.5中有。乍一看它可能是协变的(另一个新接口IReadOnlyList<T>是协变的)。不幸的是,它不是,因为它也实现IEnumerable<KeyValuePair<TKey, TValue>>并且KeyValuePair<TKey, TValue>不是协变的。

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

    void DoStuffWithAnimals<T>(IDictionary<string, T> animals) where T : Animal
    
于 2012-10-11T14:27:41.377 回答
0

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

因此,您可以自己创建接口并扩展 Dictionary 类以获得您需要的功能。

于 2012-10-11T14:24:28.067 回答