【问题标题】:C#: How can I access a child classs using a reference to the parent class?C#:如何使用对父类的引用来访问子类?
【发布时间】:2018-10-26 10:26:56
【问题描述】:

所以我有一个名为Item 的抽象类,从它继承的是一个名为Food 的类,以及其他也可以视为物品的类,例如武器和服装。

我正在尝试让 npc 在我正在研究的游戏中循环浏览他们的库存 (List<Item>) 以找到可以吃的食物,但他们将无法吃东西,除非他们知道这是食物,而不是剑或帽子。

所以我猜像if (inventory[i].GetType () == Food) 这样的东西会告诉他们它是否可以食用,但是既然我确定它是食物,我如何获得对 Food 类实例的引用?谢谢

【问题讨论】:

  • 您能否提供一些示例代码而不是口头描述?这让我们更容易理解您在做什么。

标签: c#


【解决方案1】:

使用as 运算符可以同时执行这两项操作(检查和获取类型化的引用):

foreach (var item in items)
{
    var food = item as Food;
    if (food != null)
    {
         // do whatever special things can be done with food, e.g. add an "Eat" item to some context menu
    }
}

不过,实现这一点的更好方法可能是利用多态性。 继续使用上下文菜单示例,您的 Item 基类必须有一个虚拟方法,例如 AddMenuItems,并且该方法会在您的每个项目上调用。

然后Food 类可以覆盖该方法,以将“Eat”项添加到其他类型的项没有的菜单中。

【讨论】:

    【解决方案2】:

    Food 实现IEatable。然后使用items.OfType<IEatable>():

    foreach (var eatableThing in allItems.OfType<IEatable>())
    {
        playerChar.Eat(eatableThing);
    }
    

    【讨论】:

    • 除非有可以吃的非食物物品(确实有可能!),但是有一个额外的IEatable 接口而不是直接使用OfType&lt;Food&gt; 可能有点过于复杂。
    • @O.R.Mapper:也许Food 也应该是一个抽象类,而EggBread 将是一个继承它的具体类。但所有人都是IEatable,这对 OP 很重要(“找食物吃”)。
    • 如果所有项目类型都是硬编码的,是的。如果加载具体项目,例如从资源文件和硬编码的区别仅限于一组非常基本的属性(例如是或不是食物),没有任何额外接口的微小类层次结构可能是最好的方法。这在很大程度上取决于 OP(正确,因为它不是问题的中心主题)没有告诉我们的所有因素。
    • 为什么可以吃的东西会吃掉自己?我想NPC应该吃IEatable,不是吗?无论如何,我同意这是实现这一目标的最佳方式。
    • @HimBromBeere:也许它应该是吃掉它的玩家角色。但由于我在这里没有 char 实例,所以我只是说eatableThing.Eat();。所以也许这更好:char.Eat(eatableThing);
    【解决方案3】:

    你可以这样检查:

    if (inventory[i] is Food)
    {
       ...
    }
    

    如果对象是FoodFood 的子类,上述将返回true。

    然后你可以直接转换它并调用它的方法:

    Food food = (Food)inventory[i];
    food.Eat();
    

    【讨论】:

    • 使用新的 roslyn 功能,您可以实际执行 if (inventory[i] is Food food),此时将填充可变食物
    • 我相信那是 C# 7 及更高版本,iirc。如果 OP 使用 Unity3D,那么它可能不可用,但这只是猜测。
    • @SwiftingDuster 你可以在unity中使用你喜欢的任何版本,就像创建一个普通的c#项目一样。
    【解决方案4】:

    如果 Item 有一个名为 IsEdible 的属性,那么库存中的所有其他项目都必须实现它,但当然,只有 Food 返回 true。

    interface IItem
    {
          bool IsEdible {get;}
    }
    
    class Food : IItem
    {
        public bool IsEdible => true;
        public void Eat();
    }
    
    class Gun : IItem
    {
          public bool IsEdible => false;
    }
    
    
    ...
    {
       IItem item = ...
       if(item.IsEdible)
       {
           Food food = (Food)item;
           food.Eat();
       }
    }
    

    我还要评论说,这种类型的 OOP 习语有点打破,因为您不应该对具体类型进行强制转换。如果可能,应该使用interface 函数。

    【讨论】:

    • 将 IsEdiable 保存在一个名为 IEatable 的单独接口中会比直接将其实现到 IItem 更好
    • @VimalCK:这很大程度上取决于 OP 在做什么。例如,用几十个小接口乱扔代码库并不一定比把所有东西都放在一个严格的类中更好。如果 OP 的游戏区分了几个只有几个基本属性的项目(面包、米饭、肉等具体项目,无论如何可能不是硬编码的,而是从设置或资源文件动态加载的) ,然后在通用的IItem 接口上拥有相关的区别属性,例如IsEdible,可以说是最好的方法。
    【解决方案5】:

    您需要将对象转换为特定类型!有两种方法可以做到这一点

    • 直接转换,例如:var f = (Food)o;
    • as 运算符并检查对象是否为空

    所以在你的情况下:

    • var f = (Food)inventory[i];
    • 或在 O.R.Mapper var food = item as Food; 的响应中

    【讨论】:

      【解决方案6】:

      如果您在版本 7 或更高版本(7.1、7.2 至日期)中使用 C#,您可以使用新的模式匹配功能使您的代码更加优雅:

      public class Item { }
      public class Food : Item
      {
          public void Eat() { }
      }
      public class Clothes : Item
      {
          public void Wear() {}
      }
      
      public void DecideWhatToDo(Item item)
      {
          switch (item)
          {
              case Food f:
                  f.Eat();
                  break;
              case Clothes c:
                  c.Wear();
                  break;
          }
      }
      

      您还可以对您的商品进行通用过滤:

      public List<T> Filter<T>(this List<Item> items) where T : Item
      {
          List<T> result = new List<T>();
          foreach(var item in items)
              switch (item)
              {
                  case T i:
                     result.Add(i);
                     break;
              }
      
           return result;
      }
      

      然后你像这样:items.Filter&lt;Food&gt;() 并且你将结果过滤并转换为所需的类型。

      另一种避免强制转换和反射的方法是对您的项目进行建模以提供可接受的用法列表:

      //can be a class as well if You need it to be
      public interface Item 
      {
          List<Usage> PossibleUsages {get;}
          void Do(Usage whatToDo)     
      }
      
      public enum Usage
      { Eat, Drink, Wear, ThrowAway }
      
      public class Food : Item
      {
          public List<Usage> PossibleUsages
          {
              get => new List<Usage> { Usage.Eat }
          }
      
          public void Do(Usage whatToDo)
          {
              switch(whatToDo)
              {
                  case Usage.Eat: Eat(); break;
                  default: throw new ArgumentException("Unsupported action", nameof(whatToDo));
              }       
          }
      
          private void Eat()
          {
              //Your stuff
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-03-01
        • 2017-10-12
        • 1970-01-01
        相关资源
        最近更新 更多