如何将多个对象添加到一个数组中?

4

我正在为一款游戏设计一个非常简单的库存系统。但是遇到了障碍,因为我的库存(特定类型的数组)需要接受多种类型的物品。以下是我的代码:

IWeapon[] inventory = new IWeapon[5];

public void initialiseInventory(IWeapon weapon, IArmour armour)
{
    inventory[0] = weapon; // Index 0 always contains the equipped weapon
    inventory[1] = armour; // Index 1 always contains the equipped armour
}

我会得到一个错误,指出数组无法将防具对象转换为武器对象(这是数组类型)。然后我想我可以制作一个超类(确切地说是接口),让IWeapon和IArmour从中继承。但是我又遇到了另一个错误...

IItem[] inventory = new IItem[5];

public void initialiseInventory(IWeapon weapon, IArmour armour)
{
    inventory[0] = weapon; // Index 0 always contains the equipped weapon
    inventory[1] = armour; // Index 1 always contains the equipped armour

    Console.WriteLine("The weapon name is: " + inventory[0].Name) // Problem!
}

由于数组类型是IItem,它只包含来自IItem的属性和方法,而不是来自IWeapon或IArmour的。因此问题出现了,我无法访问位于子类(子接口)IWeapon中的武器名称。有没有办法将其重定向以查找子接口(IWeapon或IArmour)而不是超级接口(IItem)中的属性?我是否在正确的道路上?


Iweapon和IArmor需要从IItem派生才能使您的第二个示例起作用。 - Lee Harrison
你可以使用“列表泛型”。它将允许您存储所有“对象”相关的信息。 - Greg
这个问题可能会给你一些灵感来定义你的接口。[这个问题] - Ken Kin
@KenKin 确定你链接的是正确的问题吗?我看不出相关性。 - Jeppe Stig Nielsen
@Jeppe Stig Nielsen:是的,我确定。我认为协变接口定义可能会给你带来一些灵感。 - Ken Kin
9个回答

11

由于第一个项目将始终是武器,第二个将始终是盔甲,因此您不应该使用数组(或任何数据结构)。只需拥有两个单独的字段,一个保存武器,另一个保存盔甲实例。

private IWeapon weapon;
private IArmour armor;

public void initialiseInventory(IWeapon weapon, IArmour armour)
{
    this.weapon = weapon;
    this.armor = armor;
}

4
如果他确实只有一件武器和一件装甲,我不会反对你在这里提出的内容,但问题的核心似乎是关于物品的多态性...... - Immortal Blue
1
@ImmortalBlue 看起来这是一个使用多态来解决不适合它的问题的问题。如果你想展示多态的用途,找一个它有效解决的问题来解决。 - Servy

2
这是一个有趣(而且常见)的难题。您已经正确解决了第一部分:为了将元素存储在单个数组中,数组类型必须匹配进入数组的所有元素的最近公共祖先。当然,这限制了功能仅适用于该公共祖先所提供的功能,而显然在您的情况下不够用。
第二部分(即,一旦您将它们全部放入数组中要怎么处理元素)就比较困难了。您需要使用类型转换或多重分派。类型转换很容易:只需在元素前面添加 (IWeapon) 即可。
((IWeapon)inventory[0]).Name

对于多个项目,您可以使用LINQ:

foreach (IWeapon w in inventory.OfType<IWeapon>()) {
    Console.WriteLine("The weapon name is: " + w.Name);
}

多重分发是更加复杂的。它允许您针对多个对象使方法虚拟化。但与此同时,您必须牺牲语言所提供的简单性:调用方法需要创建特殊的对象,而不是直接调用方法。参考访问者模式,了解如何处理多重分发。


2
你可以使用is操作符来确定一个变量是否实现了特定的接口,然后将该变量转换为该接口的实例。
        if (inventory[0] is IWeapon)
        {
            IWeapon myWeapon = (IWeapon)inventory[0];
            Console.WriteLine("The weapon name is: " + myWeapon.Name);
        }

1
在父类/接口中,您需要决定哪些常见的操作/属性真正适合放在这里。可能值得将接口设计成如下所示:
Interface IItem  
{
    string name {get};
    string itemType {get};

}

然后你只需要去。
foreach(Iitem anItem in itemArray)
{
    Console.WriteLine("The " + anItem.itemType + " is: " + anItem.Name);
}

它并不完美,也会对您的模型提出疑问,但这只是一个值得思考的东西。


问题在于物品和武器很可能不会真正可互换。在这种情况下,它们需要是可互换的才能正常工作。 - Servy
2
这是对楼主问题的回答。 - bas
1
好的,这是基于一个假设,问题似乎是关于混合列表的。我认为这些评论只是暂时的,而他试图弄清楚如何使用混合列表...(我闻到了冒险游戏的库存;)) - Immortal Blue

0

虽然我完全同意Servy所给出的答案:

如果你真的非常想使用一个数组(或者一个List<IITem>?),你只需要在IItem接口中添加Name属性。

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

我怀疑这不会对你长远有所帮助,所以我会选择Servy的答案。


0

武器和盔甲都有名称,因此这是应该放在 IItem 接口中的属性。

此外,Servy 所说的很有道理,为不同类型的物品创建数组并没有太多意义,因为特定位置始终具有相同类型的物品。

如果您想将它们作为数组访问,可以创建一个既具有数组又允许您按特定类型访问已装备物品的类:

public class Inventory {

  public IWeapon CurrentWeapon { get; }
  public IArmour CurrentArmour { get; }

  private IItem[] _items = new IItem[8];

  public IItem[int idx] {
    get {
      return
        idx == 0 ? CurrentWeapon :
        idx == 1 ? CurrentArmour :
        idx_items[idx - 2];
    }
  }

}

0

我必须发表我的意见,因为我认为这个问题问得很好,而且可能是一个常见的问题。

我会选择类似于这样的东西:

interface IItem
{
    //some common properties / methods
}

interface IWEapon : IItem
{
    string Name { get; set; } //maybe this should go to IItem? depends on your actual objects, of course
    //some other wepaon specific properties / methods
}

interface IArmor : IItem
{
    //some properties / methods
}

class Inventory
{
    public Inventory(IWEapon startWeapon, IArmor startArmor)
    {
        CurrentWeapon = startWeapon;
        CurrentArmor = startArmor;

        //optional:
        m_items.Add(startWeapon);
        m_items.Add(startArmor);
    }

    private List<IItem> m_items = new List<IItem>();
    IEnumerable<IItem> InventoryItems
    {
        get { return m_items; }
    }

    void AddItem(IItem item)
    {
        m_items.Add(item);
    }

    IWEapon CurrentWeapon
    {
        get;
        set;
    }

    IArmor CurrentArmor
    {
        get;
        set;
    }
}

为什么要为库存设计一个类?因为你可以更轻松地添加像TotalWeight或ItemCount属性等内容,而不是只有一个IItem数组。

0

假设您确实需要一个数组来存储不同的库存项目(而不仅是两个独立的字段),那么似乎您可以使用继承。我在武器和护甲中添加了不同的属性以澄清用例。

基类:

abstract class Item
{
    public string Name { get; set; }
}

派生类型:

class Weapon : Item
{
    public int Power { get; set; }
}

class Armor : Item
{
    public int Resistance { get; set; }
}

使用示例:

Item[] inventory = new Item[2];
inventory[0] = new Weapon { Name = "Singing Sword", Power = 10 };
inventory[1] = new Armor { Name = "Chainmail Shirt", Resistance = 5 };

// Note below that the Name property is always accessible because it is defined in the base class.

// Traverse only Weapon items
foreach (Weapon weapon in inventory.OfType<Weapon>())
{
    Console.WriteLine(weapon.Power);
}

// Traverse only Armor items
foreach (Armor armor in inventory.OfType<Armor>())
{
    Console.WriteLine(armor.Resistance);
}

// Travers all items
foreach (Item item in inventory)
{
    Console.WriteLine(item.Name);
}

你可以使用 OfType 进行查询,而不是 Where is - Servy

0
我发现在接口和类之间有一些混淆,但是无论如何,你应该非常简单地确保IItem具有Name属性(如果它是一个接口,则IWeapon和IArmour需要实现它),而不是将Name属性放在每个子类上(没有所谓的“子接口” :)) 也许你应该发布你的接口/类的代码....

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