如何将字典<int, child> 转换为字典<int, parent>?

10

假设我有:

public class Animal
{
    virtual public void Attack() {};
}
public class Lion : Animal
{
    public override void Attack() { base.Attack(); }
}
public class Boar : Animal
{
    public override void Attack() { base.Attack(); }
}

还有这两种动物的容器:

Dictionary<int, Lion> lions;
Dictionary<int, Boar> boars;

有没有一种方法可以将“lions”和“boars”转换,以便我可以将它们传递到这个函数中?

void IterateTable(Dictionary<int, Animal> dictionary)
{
    foreach(var entry in dictionary)
        entry.Value.Attack();
}

是的,重新创建表可能不可行,因为我将有很多表。如果不能进行转换,我可能需要重新设计这个。谢谢! - greenbit
定义“很多”。除非你谈论的是千位数,否则你不太可能看到明显的性能下降。 - 3Dave
2
或者只需使函数本身通用,以便接受这些字典中的任何一个:void IterateTable<T>(Dictionary<int, T> dictionary) where T : Animal - Alex Skalozub
1
不确定为什么你不一开始就使用 Dictionary<int,Animal> 并跳过转换的麻烦。多态/虚拟分发会确保调用正确的 .Attack() 方法。说到这,为什么不直接使用 List<Animal> 呢? - 3Dave
7个回答

7
也许是这样的吗?
void IterateTable<T>(Dictionary<int, T> dictionary)
    where T : Animal
{
    foreach(var entry in dictionary)
        entry.Value.Attack();
}

5

你的代码已经能够按照编写的方式工作。当从字典中取出 Animal 对象的值,并调用它的 Attack() 方法时,会自动调用相应的、特定于该动物的方法。这被称为 协变性。你可以为字典提供比其签名所需更具体的类型。

为了查看效果,你可以按以下方式修改你的代码:

void Main()
{
    Dictionary<int, Animal> dictionary = new Dictionary<int, Animal>()
    {
        [1] = new Lion(),
        [2] = new Boar()
    };

    IterateTable(dictionary);
}

public class Animal
{
    virtual public void Attack() { Console.WriteLine("Default animal attack"); }
}
public class Lion : Animal
{
    public override void Attack() { Console.WriteLine("Lion attack"); }
}
public class Boar : Animal
{
    public override void Attack() { Console.WriteLine("Boar attack"); }
}

void IterateTable(Dictionary<int, Animal> dictionary)
{
    foreach (var entry in dictionary)
        entry.Value.Attack();
}

输出:

狮子攻击

野猪攻击


你不能将 Dictionary<int, Lion> 赋值给 Dictionary<int, Animal>,因为 Dictionary<TKey,TValue> 泛型类型是不变的。 - Jonathon Chase
从面向对象的角度来看,这确实是最好的答案。我唯一会做的“改进”就是使用通用接口来实现解决方案,但那可能会使事情变得过于复杂化。 - Colin Young
@JonathonChase:没错,不过在一般情况下,你可以声明一个类型参数(属于你控制的类/方法)为协变 out T 或者逆变 in T - Eric J.

1
问题在于,如果您传入一个字典,那么没有任何阻止IterateTable向其中添加元素的措施。这是一个问题。由于IterateTable认为它是一个Dictionary<Animal>,因此它可以添加野猪、狮子或两者。这显然行不通。
void IterateTable(Dictionary<int,Animal> dictionary)
{
    dictionary.Add(1, new Boar()); //This would fail if you'd passed in a dictionary of Lions
    dictionary.Add(2, new Lion()); //This would fail if you'd passed in a dictionary of Boars
}

由于 IterateTable 实际上不需要向字典中添加任何元素,因此可以更改其签名,使其接受只读对象,例如 IEnumerable<T>。因为该接口禁止添加任何内容,所以是合法的,编译错误消失了。

void IterateTable(IEnumerable<Animal> animals)
{
    foreach (var entry in animals)
        entry.Attack();
}

Now you can write this, and it'll compile fine:

var lions = new Dictionary<int, Lion>();
var boars = new Dictionary<int, Boar>();
IterateTable(lions.Values);
IterateTable(boars.Values);

顺便说一下,这个概念被称为covariance,是理解泛型的一个非常重要的部分。

1

Dictionary<TKey,TValue>是一个IEnumerable<KeyValuePair<TKey,TValue>>

因此,简单的方法是使用`ToDictionary()`,只需在值选择器中向上转换值:

Dictionary<string,Lion> dict  = new Dictionary<string,Lion>( GetMeSomeLions() );

Dictionary<string,Animal> dictAnimals = dict.ToDictionary(
  kvp =>          kvp.Key,          // Key selector
  kvp => (Animal) kvp.Value // Value selector
);

0

如果将容器从以下内容更改:

Dictionary<int, Lion> lions;

Dictionary<int, Animal> lions;

如果是一个无法启动的程序,那么我会按照以下方式进行调用:
IterateTable(lions.ToDictionary(
                k => k.Key,
                v => (Animal)v.Value)
            ));

0

我认为你想要的是“限制”需要攻击的动物种类。

    void IterateTable<T>(Dictionary<int, Animal> animals) where T : Animal
    {
        foreach (KeyValuePair<int, Animal> entry in animals)
        {
            if (entry.Value is T)
            {
                entry.Value.Attack();
            }
        }
    }

    void IterateTable(Dictionary<int, Animal> animals)
    {
        foreach (KeyValuePair<int, Animal> entry in animals)
        {
            entry.Value.Attack();
        }
    }

然后你可以按照以下方式调用它们

        IterateTable<Boar>(animals);
        IterateTable(animals);

0

使用 Cast<>

IterateTable(
    lions.Cast<KeyValuePair<int, Animal>>()
         .ToDictionary(x => x.Key, x => x.Value));

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接