C# 在处理继承和集合时,是否有Is/as的替代方案?

C# 在处理继承和集合时,是否有Is/as的替代方案?,c#,C#,我有以下Item类型的类层次结构: 项目 武器:物品 盔甲:物品 我使用Item类型的通用链表。我插入了不同种类的物品:盔甲、武器等。在本例中,我坚持使用两种物品类型 我有以下代码: // Create the map. The underneath data structure is: // Array2D<LinkedList<Item>> m_map; // A generic 2d array contains a linked list of type c

我有以下Item类型的类层次结构:

项目

  • 武器:物品

  • 盔甲:物品

我使用Item类型的通用链表。我插入了不同种类的物品:盔甲、武器等。在本例中,我坚持使用两种物品类型

我有以下代码:

// Create the map. The underneath data structure is: 
// Array2D<LinkedList<Item>> m_map;
// A generic 2d array contains a linked list of type cell at each cell.
// This makes it easy to drop and pick up items at a given player position.
Map map = new Map(10, 10);

// First, I store two items at position (0,0) on my map.
map.AddItem(0, 0, new Weapon(6));
map.AddItem(0, 0, new Armor(3));

// Now, this only retrieves weapons. 
var qItems = from w in map.GetItems(0, 0)
        where w is Weapon
        select w;

// Weapons are outputted
foreach (Weapon weapon in qItems)
    Console.WriteLine(weapon.m_damage);
有没有比使用Is/as更有效的方法检索不同的项目类型?我认为这可能会导致bug,因为如果开发人员添加了一个新的项目类型,但忘记在foreach块中为其设置条件,该怎么办?正如您所猜测的,该列表可以包含多种不同的类型(在本项目中大约有10种)

编辑:这不是重复的问题。我正在查看所有子类,而可能的重复消息集中在检索一个子类上

通常在这种情况下使用。但是,您将始终拥有解析子类的内容

您的选择是以这样一种方式构建项目,即它们知道要做什么

所以您只需执行
Console.WriteLine(item.Stats)


Stats可以是一个新类
ItemStats
,它非常灵活,可以存储许多统计数据的键值对您不能真正做到这一点的原因如下:

虽然您可以使用这样的示例来查找继承
Item
的所有类,但无法真正实现自动化,因为每个类都需要访问不同的变量

以发布的代码为例:

// All items are outputted
foreach (Item item in map.GetItems(0, 0))
{
    if (item is Weapon) 
        Console.WriteLine("Damage: {0}", (item as Weapon).m_damage); 
    else if (item is Armor)
        Console.WriteLine("AC: {0}", (item as Armor).m_ac);
} 
在此代码中,您可以访问
项目作为武器
字段
m_伤害
。但是,这会随着下一行的变化而变化,因为您不会查找字段
m_damage
,而是将
m_ac
作为盔甲查找在
物品内部。除了实际定义哪些类
可以是
类型
之外,它无法实现自动化

我可以提出的一个建议是,您可以在
Item
类中创建包含
m_-damage
m_-ac
的属性,然后在继承的类中覆盖它们。这将允许您访问这些字段,而无需检查它们的
类型

public class Item
{
    public virtual int m_damage { get; set; }
}

public class Weapon : Item
{
    public override int m_damage => 4;
}
或者,正如@ragyaiddo所指出的,更好的解决方案是在
类中创建一个名为
PrintInfo()
的方法,然后将
覆盖到继承的类中,该类只需打印出有关该
的信息

public abstract class Item
{
    public abstract void PrintStats();
}

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

    public override void PrintStats()
    {
        Console.WriteLine("Damage: {0}", Damage);
    }
}

在面向对象编程中,我们依靠虚拟调度来进行竞价,因此我通常不使用
is
关键字来确定对象的类型。我相信,如果你有一个类似于以下的遗传,那么你就可以做到

// items are outputted
foreach (var item in qItems)
    Console.WriteLine(item.ShowStats());
}
项目继承:

public abstract class Item {

    public abstract string ShowStats();
}
那么盔甲就会被摧毁

public class Armor : Item {
    private readonly double _armor;

    public Armor(double armor) {
        _armor = armor;
    }

    public override string ShowStats() {
        return $"Armor: {_armor}";
    }
}
武器将是

public class Weapon : Item {
    private readonly double _damage;

    public Weapon(double damage) {
        _damage = damage;
    }

    public override string ShowStats() {
        return $"Damage: {_damage}";
    }
}
正如用linq回答的那样

foreach (Weapon item in map.GetItems(0, 0).OfType<Weapon>())
    Console.WriteLine("Damage: {0}", item.m_damage); 

foreach (Armor item in map.GetItems(0, 0).OfType<Armor>())
    Console.WriteLine("AC: {0}", item.m_ac);

正如我在评论中指出的,您可以在
Item
类中定义一个抽象方法,并让继承类实现它们的特定实现。我创建了一个示例实现。这与@Emrah Süngü的解决方案类似


根据您的需求,您可以通过或的实现进一步实现这一点。当您有多个类需要遵循相同的通用工作流时,这些模式是适用的,但这些类中的每个类都有所述工作流的不同内部实现。

您的意思是ItemStats只是一个类,而不是10+个子类?很可能,在OO中,用一种可以配置事物以获得变体的方法来代替子类化是好的(这是组合而不是继承的一部分)。如果您的子类所做的唯一事情就是提供统计信息,那么这是一个好的选择。但如果它们也提供了新的行为,则可能需要一种不同的方法。我可能会用统计数据和行为来编写东西我可以想象,以后编写统计数据和行为时需要这种灵活性。你觉得我应该保持现状吗?我不知道。这是一个与您的总体设计有关的更大的问题。如果你有10多个子类,那么你必须弄清楚它们是如何相互作用的,然后你可能会发现你的设计非常笨重,很难处理。乍一看,我认为子类有助于保持事物的有序性。否则,在呈现或返回统计数据之前,在一个类中需要测试大量的条件。我想我得看看情况如何,如果需要的话,以后再换。我喜欢这样做作为一种选择。有一件事我应该提到:如果(物品是武器)控制台,你在哪里写了
。WriteLine(…,(物品是武器)。m#damage)
可以很容易地更改为
如果(物品是武器w)控制台。WriteLine(…,w.m#damage)
@Frontear取决于他们使用的是哪个c版本,我从来不知道你能做到!容易多了。谢谢分享。我用的是C#7.2。@Frontear不,这不正确
is
关键字在框架中已经存在很长时间了。与
is
关键字匹配的模式来自
c#7
。您可以很容易地测试它。您不能在
中定义一个方法,比如
PrintInfo()
,继承类有自己的实现吗?因此,您不必显式地强制转换它们。因此,在您的foreach中,您可以直接调用
item.PrintInfo()
OP询问收集所有项目的情况。很有趣。我从未想过在foreach中使用“var”。我以前试过普通的“Item”而不是“var”,但遇到了问题。Var解决了这些转换问题。我认为原因是var使用IEnumerable获得了一个已生成的对象,但我可能是错的。@Bob只要编译器能够确定在Item类中创建属性时可以使用的对象类型:
var
:是的,我想我必须使用“is”进行转换,以查看该属性是否有效。否则,将返回类型为“剑”的无效属性,如“盔甲类”
foreach (Weapon item in map.GetItems(0, 0).OfType<Weapon>())
    Console.WriteLine("Damage: {0}", item.m_damage); 

foreach (Armor item in map.GetItems(0, 0).OfType<Armor>())
    Console.WriteLine("AC: {0}", item.m_ac);
public class Item { }
public class Weapon : Item
{
    public int Damage { get; set; }

    public override string ToString()
    {
        return $"Damage: {Damage}";
    }
}

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

    public override string ToString()
    {
        return $"AC: {AC}";
    }
}

foreach (Item item in map.GetItems(0, 0))
    Console.WriteLine(item.ToString());