thexbuttonisntworking
thexbuttonisntworking

Reputation: 51

Add subtype objects to list of supertype, then return list of subtypes from list of supertypes

I have 3 interfaces.

public interface IItem
{
    string Name { get; set; }
}

public interface IEquipable : IItem
{
    void Equip();
}

public interface IConsumable : IItem
{
    void Use();
}

IEquipable is implemented by the classes Helmet and Bow, and IConsumable is implemented by classes Potion and Food.

Then, I have a class with a property which contains a List of IItem, and proceed to add a few items of both IEquipable and IConsumable after instantiating it.

public class Character
{
    public List<IItem> Items { get; private set; }

    public Character()
    {
        this.Items = new List<IItem>();
    }

    public void AddItem(IItem item)
    {
        this.Items.Add(item);
    }
}

Program.cs

...
Character char = new Character();
char.AddItem(new Potion());
char.AddItem(new Food());
char.AddItem(new Helmet());
char.AddItem(new Bow());
...

Is there a way I can get a List of all IEquipable members from the List of IItems, each AS IEquipable?

I want to do something like

    ...
    List<IEquipable> equipmentList = //do something to char.Items and get all items of type IEquipable.
    IEquipment equipment = equipmentList.First(...)
    equipment.Equip();
    ...

I've tried using List<IEquipable> equipmentList = char.Items.OfType<IEquipable>().ToList() but the resulting list ends up empty.

Upvotes: 0

Views: 231

Answers (3)

thexbuttonisntworking
thexbuttonisntworking

Reputation: 51

So it does work like I wanted. My actual code just had another issue and I'm a dummy for not actually posting that. So here it is, for future reference.

using System.Collections.Generic;
using RolePlayGame.Library.Items.Backstage;
using System.Linq;
using System.Text;
using System;

namespace RolePlayGame.Library.Characters.Backstage
{
    public class Inventory
    {
        public List<IItem> StoredItems { get; private set; }
        public List<EquippedItem> Gear { get; private set; }

        public Inventory()
        {
            this.StoredItems = new List<IItem>();
            this.Gear = new List<EquippedItem>();
        }

        public bool HasItem(string name)
        {
            return this.StoredItems.Exists(item => item.Name == name);
        }

        public bool HasItem(IItem item)
        {
            return this.StoredItems.Contains(item);
        }

        public void RemoveItem(string name)
        {
            int firstIndex = this.StoredItems.FindIndex(item => item.Name == name);
            if (firstIndex != -1)
            {
                this.StoredItems.RemoveAt(firstIndex);
            }
        }

        public void RemoveItem(IItem item)
        {
            int firstIndex = this.StoredItems.IndexOf(item);
            if (firstIndex != -1)
            {
                this.StoredItems.RemoveAt(firstIndex);
            }
        }

        public void AddItem(IItem item, int quantity)
        {
            for (int i = 0; i < quantity; i++)
            {
                this.StoredItems.Add(item);
            }
        }

        public void AddItem(IItem item)
        {
            this.StoredItems.Add(item);
        }

        public bool CheckEquipmentSlot(EquipmentSlot slot)
        {
            return this.Gear.Exists(item => item.UsedSlots.Contains(slot));
        }

        public bool HasEquipment(IEquipment equipment)
        {
            return this.Gear.Exists(item => item.Item == equipment);
        }

        public void AddEquipment(IEquipment equipment)
        {
            IEquipment alreadyEquipped;
            foreach (EquipmentSlot slot in equipment.SlotsUsed)
            {
                if (this.Gear.Exists(item => item.UsedSlots.Contains(slot)))
                {
                    alreadyEquipped = this.Gear.Find(item => item.UsedSlots.Contains(slot)).Item;
                    this.RemoveEquipment(slot);
                    this.StoredItems.Add(alreadyEquipped);
                }
            }
            EquippedItem newEquipment = new EquippedItem(equipment);
            this.Gear.Add(newEquipment);
        }

        public void RemoveEquipment(EquipmentSlot slot)
        {
            this.Gear.RemoveAll(equipment => equipment.UsedSlots.Contains(slot));
        }

        public int GetAttributeBonusTotal(AttributeType attribute)
        {
            int bonusTotal = 0;
            foreach (IEquipment item in this.StoredItems.OfType<IEquipment>().ToList())
            {
                bonusTotal += item.GetAttributeBonus(attribute);
            }
            return bonusTotal;
        }

        public int GetCarryWeight()
        {
            int totalWeight = 0;
            foreach (IItem item in StoredItems)
            {
                totalWeight += item.Weight;
            }
            return totalWeight;
        }

        public string GearToString()
        {
            StringBuilder builder = new StringBuilder();
            builder.Append("    Equipped Gear:");
            foreach (EquippedItem equipment in this.Gear)
            {
                builder.Append($"\n        {equipment.Item.Name}");
            }

            return builder.ToString();
        }

        public string ItemsToString()
        {
            StringBuilder builder = new StringBuilder();
            builder.Append("    Inventory:");
            foreach (IItem item in this.StoredItems.Distinct())
            {
                builder.Append($"\n        {item.Name} x {this.StoredItems.FindAll(value => value == item).Count()}");
            }

            return builder.ToString();
        }

        public int GetDefenseRateAgainstTypeTotal(DamageType againstType)
        {
            int rate = 0;
            List<IOutfit> outfits = this.Gear.Select(value => value.Item).OfType<IOutfit>().ToList();
            foreach (IOutfit item in outfits)
            {
                rate += item.GetDefenseRateAgainstType(againstType);
            }
            return rate;
        }
    }
}

One of the last lines has the problem (now fixed). List<IOutfit> outfits = this.Gear.Select(value => value.Item).OfType<IOutfit>().ToList(); used to be List<IOutfit> outfits = this.Gear.OfType<IOutfit>().ToList();. But Gear is of type List<EquippedItem>, and EquippedItem is not an implementation of IItem.

Here is EquippedItem.cs

using RolePlayGame.Library.Items.Backstage;
using System.Collections.Generic;

namespace RolePlayGame.Library
{
    public class EquippedItem
    {
        public List<EquipmentSlot> UsedSlots { get; set; }
        public IEquipment Item { get; set; }

        public EquippedItem(IEquipment equipment)
        {
            this.Item = equipment;
            this.UsedSlots = equipment.SlotsUsed;
        }
    }
}

I needed to select the Item property from the items inside Gear as another list before doing the type filtering with .OfType<IOutfit>(). That's where .Select(value => value.Item) enters the stage.

So that's that. I'll learn to post actual code for future questions.

Upvotes: 1

Enigmativity
Enigmativity

Reputation: 117057

I implemented (and fixed minor typos in) your code like this:

void Main()
{
    Character character = new Character();
    character.AddItem(new Potion());
    character.AddItem(new Food());
    character.AddItem(new Helmet());
    character.AddItem(new Bow());
    
    List<IEquipable> equipmentList = character.Items.OfType<IEquipable>().ToList();
}

public class Potion : IConsumable
{
    public string Name { get; set; }

    public void Use()
    {
        throw new NotImplementedException();
    }
}

public class Food : IConsumable
{
    public string Name { get; set; }

    public void Use()
    {
        throw new NotImplementedException();
    }
}

public class Helmet : IEquipable
{
    public string Name { get; set; }

    public void Equip()
    {
        throw new NotImplementedException();
    }
}

public class Bow : IEquipable
{
    public string Name { get; set; }

    public void Equip()
    {
        throw new NotImplementedException();
    }
}

public interface IItem
{
    string Name { get; set; }
}

public interface IEquipable : IItem
{
    void Equip();
}

public interface IConsumable : IItem
{
    void Use();
}

public class Character
{
    public List<IItem> Items { get; private set; }

    public Character()
    {
        this.Items = new List<IItem>();
    }

    public void AddItem(IItem item)
    {
        this.Items.Add(item);
    }
}

Your exact code (albeit char renamed to character) works perfectly fine. The equipmentList ends up with two elements. The issue you're seeing, i.e. "the resulting list ends up empty", is not reproducible with the code you've posted.

Upvotes: 1

TheGeneral
TheGeneral

Reputation: 81493

You can use the OfType method

Filters the elements of an IEnumerable based on a specified type.

Signature

public static IEnumerable<TResult> OfType<TResult> (this IEnumerable source)

Usage

var equipable = Character.Items.OfType<IEquipable>();

Or encapsulate it as a method in the instance or an extension method if you like

Upvotes: 1

Related Questions