C# Object类型字典中子类方法调用及泛型List转换问题
问题解决:List子类集合转父类集合的InvalidCastException
问题背景
我有一个存储List的Dictionary,List中的元素是继承自Item父类的Weapon、Armour、Potion子类,子类重写了GetItemData方法。Player类的inventory字典定义如下:
internal abstract class Item { protected string name = "Item"; protected int level = GameConstants.MIN_ITEM_LEVEL; protected string description = ""; protected Types type; public enum Types { armour, potion, weapon } public Dictionary<string, dynamic> GetItemData() { return new Dictionary<string, dynamic>() { { "name", name}, { "description", description }, { "type", type } }; } } // Player类中的字典 protected Dictionary<string, object> inventory = new() { {"WEAPON", new List<Weapon>() }, {"AMOUR", new List<Armour>() }, {"POTION", new List<Potion>() } // 所有子类都继承自Item,比如class Weapon:Item };
编写ListPlayerItems方法展示物品数据时,抛出异常:
System.InvalidCastException: "Unable to cast object of type 'System.Collections.Generic.List`1[Items.Weapon.Weapon]' to type 'System.Collections.Generic.List`1[Items.Item]'."
方法代码:
private void ListPlayerItems(string previewText, string itemSelector) { Console.WriteLine(previewText); int itemIndex = 0; ((List<Item>)inventory[itemSelector]).ForEach(item => { Dictionary<string, dynamic> itemData = item.GetItemData(); Console.Write($"{itemIndex}) 名称: {itemData["name"]}; 描述:{itemData["description"]}; "); if (item is Weapon) { Console.Write($"最小伤害: {itemData["minDamage"]}; 最大伤害: {itemData["maxDamage"]}\n"); } itemIndex++; }); }
问题出在((List<Item>)inventory[itemSelector])的类型转换,需要解决这个转换问题或优化实现方案。
错误原因
List<T>是不变泛型类型,即使Weapon继承自Item,List<Weapon>也不能直接转换为List<Item>——因为泛型不变性不允许这样的向上转换,编译器无法保证转换后的集合操作安全性(比如向List<Item>中添加Armour,但原集合是List<Weapon>,会导致类型错误)。
解决方案
方案1:利用协变接口转换为IEnumerable-
IEnumerable<out T>是协变泛型接口,支持将子类集合转换为父类的IEnumerable。修改转换代码即可:
private void ListPlayerItems(string previewText, string itemSelector) { Console.WriteLine(previewText); int itemIndex = 0; // 先转成IEnumerable<Item>,而非List<Item> ((IEnumerable<Item>)inventory[itemSelector]).ToList().ForEach(item => { Dictionary<string, dynamic> itemData = item.GetItemData(); Console.Write($"{itemIndex}) 名称: {itemData["name"]}; 描述:{itemData["description"]}; "); if (item is Weapon) { Console.Write($"最小伤害: {itemData["minDamage"]}; 最大伤害: {itemData["maxDamage"]}\n"); } itemIndex++; }); }
如果不需要List的额外方法,甚至可以直接遍历IEnumerable,不用转ToList:
foreach (var item in (IEnumerable<Item>)inventory[itemSelector]) { // 逻辑同前 itemIndex++; }
方案2:重构inventory的类型为Dictionary<string, List- >
直接将inventory的value类型定义为List<Item>,因为子类实例可以直接添加到父类List中,从根源避免类型转换:
// 修改Player类中的inventory定义 protected Dictionary<string, List<Item>> inventory = new() { {"WEAPON", new List<Item>() }, {"AMOUR", new List<Item>() }, {"POTION", new List<Item>() } }; // 添加物品时直接添加子类实例,比如: inventory["WEAPON"].Add(new Weapon());
这样ListPlayerItems方法中无需转换,直接使用:
inventory[itemSelector].ForEach(item => { /* ... */ });
方案3:使用OfType- 筛选元素
如果必须保留原inventory的Dictionary<string, object>类型,可以先将object转换为IEnumerable,再用OfType
private void ListPlayerItems(string previewText, string itemSelector) { Console.WriteLine(previewText); int itemIndex = 0; var items = ((IEnumerable)inventory[itemSelector]).OfType<Item>(); foreach (var item in items) { Dictionary<string, dynamic> itemData = item.GetItemData(); Console.Write($"{itemIndex}) 名称: {itemData["name"]}; 描述:{itemData["description"]}; "); if (item is Weapon) { Console.Write($"最小伤害: {itemData["minDamage"]}; 最大伤害: {itemData["maxDamage"]}\n"); } itemIndex++; } }
内容的提问来源于stack exchange,提问作者Даниил Дуров
相关产品推荐
相关产品推荐

