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)中查找属性?我是否走在正确的道路上?

4

9 回答 9

11

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

private IWeapon weapon;
private IArmour armor;

public void initialiseInventory(IWeapon weapon, IArmour armour)
{
    this.weapon = weapon;
    this.armor = armor;
}
于 2013-02-08T17:48:39.807 回答
2

您可以使用is运算符来确定变量是否实现了特定接口,然后将该变量强制转换为该接口的实例。

        if (inventory[0] is IWeapon)
        {
            IWeapon myWeapon = (IWeapon)inventory[0];
            Console.WriteLine("The weapon name is: " + myWeapon.Name);
        }
于 2013-02-08T18:07:19.177 回答
2

这是一个有趣(且常见)的谜题。您已经正确理解了它的第一部分:为了将元素存储在单个数组中,数组类型必须与进入数组的所有元素的共同祖先相匹配。当然,这将功能限制为仅由该共同祖先提供的功能,这在您的情况下显然是不够的。

第二部分(即,将元素全部放入数组后如何处理)有点困难。您需要类型转换或多次调度。类型转换很简单:只需(IWeapon)在元素前面添加:

((IWeapon)inventory[0]).Name

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

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

多分派要复杂得多。它使您可以使方法相对于多个对象成为虚拟方法。作为回报,您必须牺牲语言提供的简单性:调用方法需要制作特殊的对象,而不是直接调用方法。查看访问者模式,了解如何处理多次调度的一些想法。

于 2013-02-08T17:53:17.070 回答
1

在父类/接口中,您需要决定哪些常见的操作/属性真正意味着去这里。

可能值得拥有这样的界面:

Interface IItem  
{
    string name {get};
    string itemType {get};

}

然后你就可以走了

foreach(Iitem anItem in itemArray)
{
    Console.WriteLine("The " + anItem.itemType + " is: " + anItem.Name);
}

它并不完美,并且会引发有关您的模型的问题,但这只是需要考虑的问题。

于 2013-02-08T17:51:13.827 回答
0

我必须加上我的 2 美分,因为我发现这个问题问得很好,这可能是一个常见问题。

我会用这样的东西:

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

为什么要为库存设计一个类?因为您可以比只有一个IItem.

于 2013-02-08T18:12:55.267 回答
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);
}
于 2013-02-08T18:20:05.157 回答
0

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

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

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

我怀疑从长远来看它是否会对您有所帮助,所以我会寻求Servy的答案。

于 2013-02-08T17:50:59.507 回答
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];
    }
  }

}
于 2013-02-08T17:51:05.760 回答
0

我发现接口和类之间有点混淆,但是,您应该非常简单地确保 IItem 具有 Name 属性(如果它是接口,则 IWeapon 和 Iarmour 需要实现),而不是放置 Name 属性在每个子类上(没有“子接口”之类的东西:))也许你应该发布你的接口/类的代码......

于 2013-02-08T17:53:06.693 回答