The examples below uses cars and engines to demonstrate the point. There is one family of base class cars and subclasses of fuel cars (sorry in advance that I am too English and refuse to call it gas - it is a liquid after all). At a later point we may want to add other families of cars, for example electric cars, steam powered cars, cold fusion cars...
The problem is that a fuel car will always use a fuel engine (and vice versa) but each time I have to explicitly upcast.
- Code: Select all
class Car
{
public Engine Engine { get; set; }
}
class Engine
{
public Car Car { get; set; }
}
class FuelCar : Car
{
}
class FuelEngine : Engine
{
public bool HasFuel()
{
return true;
}
}
class Program
{
public static void Main1()
{
FuelCar car = new FuelCar();
FuelEngine engine = new FuelEngine();
car.Engine = engine;
engine.Car = car;
((FuelEngine)car.Engine).HasFuel();
}
}
I would ideally like to remove that (FuelEngine) cast.
One implementation is to create a new property on the inheriting classes.
- Code: Select all
class Car
{
private Engine engine;
public Engine Engine
{
get { return engine; }
set
{
Validate(value);
engine = value;
}
}
protected virtual void Validate(Engine value)
{
// do nothing
}
}
class Engine
{
private Car car;
public Car Car
{
get { return car; }
set
{
Validate(value);
car = value;
}
}
protected virtual void Validate(Car value)
{
// do nothing
}
}
class FuelCar : Car
{
public new FuelEngine Engine
{
get { return base.Engine as FuelEngine; }
set { base.Engine = value; }
}
protected override void Validate(Engine value)
{
if (!(value is FuelEngine)) {
throw new ArgumentException();
}
}
}
class FuelEngine : Engine
{
public new FuelCar Car
{
get { return base.Car as FuelCar; }
set { base.Car = value; }
}
protected override void Validate(Car value)
{
if (!(value is FuelCar)) {
throw new ArgumentException();
}
}
public bool HasFuel()
{
return true;
}
}
class Program
{
public static void Main2()
{
FuelCar car = new FuelCar();
FuelEngine engine = new FuelEngine();
car.Engine = engine;
engine.Car = car;
car.Engine.HasFuel();
}
}
This has the advantage that all the casting is all done in one place. However, it has now become more verbose. It is also not quite as elegant as the other solution below.
I wanted to try with generics to get rid of this verbosity and the casting altogether.
- Code: Select all
class PowerType { }
class Fuel : PowerType { }
class Car<TPower, TEngine> where TPower : PowerType where TEngine : Engine<TPower, Car]>
{
public TEngine Engine { get; set; }
}
class Engine<TPower, TCar> where TPower : PowerType where TCar : Car<TPower, Engine>
{
public TCar Car { get; set; }
}
class FuelCar : Car<Fuel, FuelEngine>
{
}
class FuelEngine : Engine<Fuel, FuelCar>
{
public bool HasFuel()
{
return true;
}
}
class Program
{
public static void Main3()
{
FuelCar car = new FuelCar();
FuelEngine engine = new FuelEngine();
car.Engine = engine;
engine.Car = car;
car.Engine.HasFuel();
}
}
The types would all work without any casting - each Car knows what type of Engine it has, and each Engine know what type of Car it is part of. HOWEVER the types of TCar and TEngine are recursive and type gets infinite. The full type would be of the form:
- Code: Select all
TEngine : Engine<TPower, Car<TPower, Engine<TPower, Car<TPower, ...>>>>
Is there a way to collapse the infinite type into a finite type? Or is there a sensible way of creating this family of classes that doesn't rely on infinite types?
Thanks.
