DoSomething(Car car);
DoSomething(Bike bike);
public class Car : Vehicle {}
public class Bike : Vehicle {}
public abstract class Vehicle {}
void run(Vehicle vehicle) {
DoSomething(vehicle);
}
This feels like a simple problem but I'm having issues. DoSomething(Vehicle vehicle) doesn't exist, so DoSomething(vehicle) throws an error, even though vehicle is "guaranteed" to be either Car or Bike. How can I convince the compiler that "vehicle" is Bike or Car, so that DoSomething can be run?
Of course I could just have another method along the lines of
DoSomething(Vehicle vehicle)
{
if(vehicle is Car) ... etc
}
But surely there is a cleaner approach?
EDIT/CLARITY
The motivation for putting this code in a manager class, rather than having DoSomething() exist in the Vehicle class, is that each Vehicle needs to access different parts of the program. For example:
DoSomething(Car car) {
motorwayInfo.CheckMotorwayStatus();
}
DoSomething(Bike bike) {
cycleInfo.CheckCyclePathStatus();
}
Not sure if this analogy is really getting across my particular issue that well, hah - but basically I don't want Car's to have any reference to cycleInfo, nor Bikes to have any reference to motorWayInfo. But, putting DoSomething into Vehicle basically means its parameter needs to either be:
DoSomething(CycleInfo cycleInfo, MotorwayInfo motorwayInfo)
or
DoSomething(InfoManager infoManager)
Neither of which seem totally ideal, as I know that each subtype is only going to use a specific info object. Am I going about this all wrong?
The real question here is - what do you expect to happen to the method's parameter? If that, whatever it is, depends on a concrete (sub)type of the parameter, then the proper place for that behavior is in the virtual (or even abstract) member of the parameter base class - Vehicle
.
class Vehicle
{
public abstract void Behave();
}
class Car : Vehicle
{
public override void Behave()
{
// Do something specific to a car
}
}
class Bike : Vehicle
{
public override void Behave()
{
// Do something specific to a bike
}
}
...
void Run(Vehicle vehicle)
{
vehicle.Behave();
}
As you can see from this code, I have reverted the roles. The Run
function is not responsible to know how a concrete parameter should behave. Instead, each concrete object passed as the Vehicle
parameter would have to know how to behave. That is the proper polymorphism.
Regarding the Run
method, all its responsibilities regarding the argument should relate to the common type of all parameter objects, and that is the base class Vehicle
. In that respect, the method could access members defined on the base class, or insert the object into a collection, etc.
void Run(Vehicle vehicle)
{
vehicle.Behave();
List<Vehicle> list = ...
list.Add(vehicle);
}
What you're looking for is called Double Dispatch and is not supported natively by the C# compiler.
You can achieve it with some refactoring though:
public void DoSomething(Car car) {}
public void DoSomething(Bike bike) {}
public abstract class Vehicle
{
// ...
public abstract void CallDoSomething();
}
public class Car : Vehicle
{
public override void CallDoSomething()
{
DoSomething(this);
}
}
public class Bike : Vehicle
{
public override void CallDoSomething()
{
DoSomething(this);
}
}
Or, if you don't care about the performance penalty, you can use the dynamic
keyword that will defer the overload resolution until runtime then chooses the most appropriate method that matches the type:
void run(dynamic vehicle)
{
DoSomething(vehicle);
}
See MSDN
vehicle is "guaranteed" to be either Car or Bike
That's not true. If all Cars are Vehicles and all Bikes are Vehicles, that does not mean that all Vehicles are either Bikes or Cars. Subtyping does not work that way.
How can I convince the compiler that "vehicle" is Bike or Car, so that DoSomething can be run?
The only way to do that is by casting, which you already mentioned:
if (vehicle is Car) (vehicle as Car).CheckMotorwayStatus();
else if (vehicle is Bike) (vehicle as Bike).CheckCyclePathStatus();
But surely there is a cleaner approach?
The approach that Zoran's answer suggested is the cleaner approach.
I don't want Car's to have any reference to cycleInfo, nor Bikes to have any reference to motorWayInfo.
With some thought, you can use it even with your slightly more complex setup. For example:
public abstract class Vehicle
{
public abstract void PrintRouteStatus();
}
public class MotorwayInfo
{
}
public class CycleInfo
{
}
public class Car : Vehicle
{
// probably pass this in via a constructor.
public MotorwayInfo _motorwayInfo = new MotorwayInfo();
public override void PrintRouteStatus()
{
Console.WriteLine(_motorwayInfo);
}
}
public class Bike : Vehicle
{
// probably pass this in via a constructor.
public CycleInfo _cycleInfo = new CycleInfo();
public override void PrintRouteStatus()
{
Console.WriteLine(_cycleInfo);
}
}
It works without Car being aware at all of Bike's info and vice versa.
public static void Main()
{
var p = new Program();
p.DoSomething(new Car());
p.DoSomething(new Bike());
}
public void DoSomething(Vehicle v)
{
v.PrintRouteStatus();
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With