Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Polymorphic overloading - how to force an abstract type to "act" as one of its derived types for the purposes of overloaded parameters?

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?

like image 886
Cerzi Avatar asked Feb 25 '18 16:02

Cerzi


3 Answers

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);
}
like image 200
Zoran Horvat Avatar answered Nov 16 '22 02:11

Zoran Horvat


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

like image 2
haim770 Avatar answered Nov 16 '22 03:11

haim770


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();
}
like image 1
Shaun Luttin Avatar answered Nov 16 '22 01:11

Shaun Luttin