【问题标题】:C# - List to hold multiple object types inherited from the same base classC# - 包含从同一基类继承的多个对象类型的列表
【发布时间】:2014-02-24 13:36:30
【问题描述】:

在下面的示例中,我希望能够将 StandardCar 或 RaceCar 的实例放入 CarCollection 列表,然后在循环 CarCollection 列表时将它们转换为正确的数据类型。

我知道这在 C# 中是不可能的。但是我找不到不涉及一个类的方法,该类在一个类中具有所有属性。由于 NumberOfRaces 仅适用于 RaceCars,这样设置看起来很混乱吗?

当我浏览该系列时,我会知道我使用的是标准汽车还是赛车。但是目前,使用下面的代码,如果我将 StandardCar 放入列表中,然后遍历该列表并将其转换为 StandardCar,我将得到一个转换异常。

请注意,我实际的最终课程比下面的要复杂得多。

class Car
{
    public string Name {get; set;}
}

class RaceCar : Car
{
    public int NumberOfRaces {get; set;}
}

class StandardCar: Car
{
    public bool ChildrenInCar {get; set;}
}    

class CarCollection
{
   List<Car> Collection {get; set;}
}

感谢您的帮助。

【问题讨论】:

  • 嗯,您可以将StandardCarRaceCar 的实例放入CarCollection.Collection 属性中。是什么让你认为你做不到?
  • 我不认为我的问题很清楚。我已编辑以提供帮助。我可以把它们放在那个列表中。但我无法遍历列表并将它们投射到 SandardCar 或 RaceCar
  • 当然你可以将它们返回StandardCarRaceCar如果它们属于那种类型。请注意,如果您将单个StandardCar 放入列表中,则不能将其转换RaceCar。这个问题还不清楚。
  • @james 你不应该这样做。您应该利用多态性,以便在使用列表时,除了基本 Car 类型所公开的内容之外,您不需要任何内容。
  • 如果你真的有你说的问题,你将StandardCar类型的对象放入列表中,然后循环遍历列表,将其转换回StandardCar,然后抛出一个例外,请发布一个简短但完整的程序来演示,因为它应该可以正常工作

标签: c# list oop


【解决方案1】:

创建接口:

public interface ICar{
//Place all common property / method definitions here
}

让 Car 实现该接口:

public class Car : ICar
{
//implementation
}

然后更改您的 CarCollection:

public class CarCollection
{
List<ICar> Collection{get; set;}
}

现在您应该能够将任何以 Car 为基础的内容添加到该列表中。枚举您的集合时,所有 ICar 都将具有您可以访问的接口中定义的那些属性/方法,而无需进行强制转换和类型检查。

【讨论】:

  • 你打败了我,但这是实现 OP 所寻求的最佳方式。
  • 为什么这是最好的方法?他通过这个实现了他现有代码没有的什么?他可以将任何对象放入他现有的List&lt;Car&gt; 集合中,只要该对象的类型为Car 或继承自它的类型。这里不需要接口。
  • 不必要的接口
  • 他已经可以做到这一点,只需将所有通用属性或方法放在基类中。
  • 这里的问题是他现有的代码没有问题。他可以做他说他想做的一切。添加接口不会改变这一点。当然,使用接口可能会让您有机会将生产对象替换为模拟或存根,但这与他的问题是正交的。
【解决方案2】:

是的,您可以将StandardCar 添加到List&lt;Car&gt; 集合中。您不能做的是在迭代集合时轻松访问派生类的特定属性。

var collection = new CarCollection();
collection.Collection= new List<Car>();

collection.Collection.Add(new RaceCar());
collection.Collection.Add(new StandardCar());

foreach (var car in collection.Collection)
    Console.WriteLine(car.Name);

工作得很好。你不能做的是:

foreach (var car in collection.Collection)
    Console.WriteLine(car.ChildrenInCar);

因为ChildrenInCar 只存在于StandardCar

【讨论】:

    【解决方案3】:

    完全有可能,你可以像往常一样添加到列表中,但是要回退需要测试返回的汽车的类型:

    // Create the cars
    RaceCar rcar = new RaceCar();
    StandardCar scar = new StandardCar();
    
    // Add to the collection
    CarCollection col = new CarCollection();
    col.Add(rcar);
    col.Add(scar);
    
    // Iterate
    foreach(Car car in col.Collection)
    {
        if(car is RaceCar)
        {
            RaceCar racecar = (RaceCar)car;
        }
        else if(car is StandardCar)
        {
            StandardCar standCar = (StandardCar)car;
        }
    
        // ... etc
    }
    

    你也可以这样做:

    car as RaceCar
    

    并确保返回的值不为空。

    注意:代码未经测试,但应该可以工作。

    【讨论】:

    • 有这样的检查代码是代码异味,表明程序设计存在问题。
    • @Servy 虽然我同意,但这是执行 OP 要求的最佳方式,无需为每种车型单独收集。
    • 我们对 OP 实际在做什么知之甚少,无法说出正确的解决方案是什么。也许是单独的集合,也许是利用多态性,也许他甚至还不知道自己想要什么。
    【解决方案4】:

    您可以检测演员表是否有效:

    List<Car> Collection;
    
    ..
    
    foreach (Car car in Collection)
    {
        RaceCar raceCar = car as RaceCar;
        if (raceCar != null)
        {
           raceCar.NumberOfRaces++;
        }
    }
    

    虽然这很糟糕。如果可能的话,最好不要“关心”特定的派生类型是什么。例如:

    foreach (Car car in Collection)
    {
        car.Update();
    }
    

    然后 RaceCar 将拥有自己的 Update() 实现,该实现与 NumberOfRaces 相关。此外,您可能不需要 NumberOfRaces 属性有一个设置器,也许您甚至不需要它公开。

    【讨论】:

      【解决方案5】:

      有不同的可能性来处理这个问题。只要有可能,您应该尽量避免强制转换。例如,如果您想以不同方式显示不同的汽车,请覆盖 ToString 方法。您还可以添加以不同方式响应不同情况的方法。在基类中将这些方法声明为抽象(并将基类也声明为抽象)。

      abstract class Car
      {
          public string Name { get; set; }
      
          public abstract void RespondToCollision();
          public abstract void RespondToLoopPerformed();
      }
      
      class RaceCar : Car
      {
          public int NumberOfRaces {get; set;}
      
          public override void RespondToCollision()
          {
              // Do nothing
          }
      
          public override void RespondToLoopPerformed()
          {
              NumberOfRaces++;
          }
      
          public override string ToString()
          {
              return String.Format("Name = {0}, # of races = {1}", Name, NumberOfRaces);
          }
      }
      
      class StandardCar: Car
      {
          public bool ChildrenInCar {get; set;}
      
          public override void RespondToCollision()
          {
              if (ChildrenInCar > 0) {
                  ChildrenInCar--;
              }
          }
      
          public override void RespondToLoopPerformed()
          {
              // Do nothing
          }
      
          public override string ToString()
          {
              return String.Format("Name = {0}, children in car = {1}", 
                                   Name, ChildrenInCar);
          }
      }    
      

      另一种可能性是测试实际类型:

      if (car is RaceCar) {
          ((RaceCar)car).NumberOfRaces++;
      } else if (car is StandardCar) {
          ((StandardCar)car).ChildrenInCar = 0;
      }
      

      另一种可能性是过滤汽车类型并分别处理它们:

      var raceCars = cars.OfType<RaceCar>();
      foreach (raceCar in raceCars) {
         raceCar.NumberOfRaces++;
      }
      

      【讨论】:

        【解决方案6】:

        另一种设计是使用接口来为各种汽车对象指定不同的逻辑边界。

        interface ICar ...
        interface IRacingCar : ICar
        interface IFlyingCar : ICar
        interface IFamilyCar : ICar
        

        然后在你的循环中,你根据它们的逻辑能力对各种汽车进行操作。

        void ProcessCars()
        {
            foreach( var car in cars )
            {
                 if(car is IRacingCar) ...
                 if(car is IFlyingCar) ...
                 if(car is IFamilyCar) ...
            }
        }    
        

        这种方法的优点是您可以拥有更多类型的类,而不必为每个类都使用 if 语句。此外,您的 if 语句一次只需要处理一个问题。

        class RaceCar: IRacingCar ...
        class FlyingRaceCar: IRaceCar, IFlyingCar ...
        class FlyingFamilyCar: IFlyingCar, IFamilyCar ...
        

        循环并不关心一辆汽车可以实现多个接口。它一次只需要处理一个问题。

        例如,任何 IFlyingCar 都可以应对重力和空中交通管制,而不管它是否有儿童乘客或它完成了多少比赛。

        这就是接口的优势。它们允许您的界面使用者只关心对象的质量。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2023-02-21
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多