Search code examples
c#ooptypesarchitecture

C# how to use child method in object type dictionary


I have a dictionary of Lists with types (Weapon, Armour, Potion) that extended from 1 parent (Item), in a child classes GetItemData overrides with different properties

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 }
        };
    }

}
//dictionary in player class
protected Dictionary<string, object> inventory
        = new()
        {
            {"WEAPON", new List<Weapon>() },
            {"AMOUR", new List<Armour>() },
            {"POTION", new List<Potion>() }
            //All are extended from Item `class Weapon:Item`
        };

I need to show some data from each list to user, for this i create a function that takes a itemSelector for dictionary, and Write data from them to console. But function that i write is not work, its throw

System.InvalidCastException: "Unable to cast object of type 'System.Collections.Generic.List1[Items.Weapon.Weapon]' to type 'System.Collections.Generic.List1[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++;
        });
    }

The problem is in Type that takes a List<> in implicit operator

     ((List<Item>)inventory[itemSelector]).ForEach(item =>

As i get it, i need somehow downcast Item to type of picked List, i dont know how to do that, please help me with that, maybe i make it wrong from start and you know better way to do this


Solution

  • C# does not support variance for classes (see this and this), so List<Item> is not List<Weapon> even if Item is base class for Weapon. For this particular use case you can workaround with non-generic IEnumerable, OfType method and ordinary foreach:

    foreach(var item in ((IEnumerable)inventory[itemSelector]).OfType<Item>())
    {
       // ...use item
    }
    

    or leverage covariance of IEnumerable<T>:

    foreach(var item in ((IEnumerable<Item>)inventory[itemSelector]))
    {
       // ...use item
    }
    

    Also I would recommend to avoid using dynamic and object typed variables or collections whenever it is possible so possibly it is worth to rework your data structures a little bit.