在下面的例子中,我希望能够将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;}
}
感谢您的帮助。
答案 0 :(得分: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
并确保返回的值不为null。
注意:代码未经过测试,但应该可以使用。
答案 1 :(得分:2)
您可以检测演员是否有效:
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属性来设置setter,也许您甚至不需要公开。
答案 2 :(得分:1)
是的,您可以将StandardCar
添加到List<Car>
集合中。在迭代集合时,您不能做的是轻松访问派生类特定属性。
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 :(得分:0)
处理这个问题有不同的可能性。只要有可能,你应该尽量避免铸造。例如,如果要以不同方式显示不同的汽车,请覆盖ToString
方法。您还可以添加以不同方式响应不同情况的方法。在基类中将这些方法声明为抽象(并且将基类delcare作为抽象)。
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++;
}
答案 4 :(得分:0)
另一种设计是使用接口为各种汽车对象指定不同的逻辑边界。
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都可以处理重力和空中交通管制,无论是否有儿童乘客或已经完成了多少次比赛。
这是接口的优点。它们允许您的界面使用者仅关注对象的质量。
答案 5 :(得分:-2)
创建一个界面:
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都将具有您可以访问的界面中定义的属性/方法,而无需进行强制转换和类型检查。