0

I have the following setup:

abstract class Vehicle with the following simplified structure:

public abstract class Vehicle
{
    public string Brand { get; set; }
    public string Model { get; set; }
    public string GetBasicInfo(Vehicle v)
    {
        return string.Format("This car was produced by {0}. The Model is {1}.", Brand, Model);
    }
    abstract public string GetVehicleData<T>(T vehicle) where T : Vehicle;
}

I then have a class Car inheriting from Vehicle with some properties of its own:

public class Car : Vehicle
{
    public bool Is4x4 {get;set;}
    public override string GetVehicleData<Car>(Car vehicle)
    {
        return String.Format("Brand: {0}, Model: {1}, Is4x4: {2}", vehicle.Brand, vehicle.Model, vehicle.Is4x4);
    }
}

When I try to access vehicle.Is4x4 I get an error, namely that Car does not contain a definition for Is4x4. I research on SO and found out, that since the property Is4x4 is not defined in the abstract parent class, it cannot be called upon in the overriden method.

To circumvent this problem I changed the 2 classes as follows:

public abstract class Vehicle
{
    public string Brand { get; set; }
    public string Model { get; set; }
    public string GetBasicInfo(Vehicle v)
    {
        return string.Format("This car was produced by {0}. The Model is {1}.", Brand, Model);
    }
    abstract public string GetVehicleData(Vehicle vehicle);
}

And the car class:

public class Car : Vehicle
{
    public bool Is4x4 {get;set;}
    public override string GetVehicleData(Vehicle vehicle)
    {
        Car vehicleCast = (Car)vehicle;
        return String.Format("Brand: {0}, Model: {1}, Is4x4: {2}", vehicleCast.Brand, vehicleCast.Model, vehicleCast.Is4x4);
    }
}

This code compiles and allows me to add the child class specific properties in the GetVehicleData Method. However I feel like there should be a better way to solve this common problem.

Stefan
  • 59
  • 8
  • Why does `GetVehicleData` require a parameter in the first place? What is it supposed to do? Is it not supposed to get the data of `this` vehicle? Suppose you call `vehicleA.GetVehicleData(vehicleB)`. Which vehicle's data would you expect to get? – Sweeper Jan 05 '22 at 09:43

1 Answers1

3

The problem is this line:

public override string GetVehicleData<Car>(Car vehicle)

This is the same as writing:

public override string GetVehicleData<T>(T vehicle)

... except that you've used Car as the name of the type parameter, rather than the more conventional T. Car there does not refer to the class Car, instead it refers to a new generic type parameter.

When you override a generic method, the language doesn't force you to use the same type parameter names: you could write

public abstract class Vehicle
{
    public abstract string GetVehicleData<T>(T vehicle) where T : Vehicle;
}

public class Car : Vehicle
{
    public abstract string GetVehicleData<T2>(T2 vehicle) { ... }
}

... and you've chosen to use Car instead of T2.


To address the underlying problem, you can't define the base method Vehicle.GetVehicleData(Vehicle vehicle), and then override that with a more specific Car.GetVehicleData(Car car). What would be called if someone did new Car().GetVehicleData(new Truck())?

Car's GetVehicleData method needs to be able to accept any type of Vehicle.

That said, it's unclear why your GetVehicleData is taking a second vehicle, rather than just operating on the instance it's defined on.

It would be more normal to write something like:

public abstract class Vehicle
{
    public string Brand { get; set; }
    public string Model { get; set; }
    public string GetBasicInfo()
    {
        return string.Format("This car was produced by {0}. The Model is {1}.", Brand, Model);
    }
    public abstract string GetVehicleData();
}

public class Car : Vehicle
{
    public bool Is4x4 {get;set;}
    public override string GetVehicleData()
    {
        return String.Format("Brand: {0}, Model: {1}, Is4x4: {2}", Brand, Model, Is4x4);
    }
}
canton7
  • 37,633
  • 3
  • 64
  • 77
  • The GetVehicleData could indeed be reformulated to just use its own properties instead of taking a parameter. I simplified too much here. My actual method needs to take another Car as input. As you say, theoretically the Method GetVehicleData in the Class Car can be called passing any type of Vehicle, and that's why I get the compiler error. Is it possible to limit the parameter to just taking Cars? Eg. I know all my Vehicles should have a Method "CopyDataFromSameVehicle". Depending on the type of vehicle, I would then like to copy the specific properties (Is4x4, HasHelmet ecc). – Stefan Jan 05 '22 at 09:55
  • @Stefan If you do that, you can't define it as an abstract method on `Vehicle`. Think about it: if someone does `Vehicle vehicle = new Car()`, they need to be able to call `vehicle.GetVehicleData(...)`. They've got a variable of type `Vehicle`, even though the actual runtime type is `Car`. If the signature of `Vehicle.GetVehicleData` says that it can accept any type of `Vehicle`, then people can call it and pass in any type of `Vehicle`, and the runtime type needs to be able to handle that – canton7 Jan 05 '22 at 09:58
  • Would the right approach then be to create a GetVehicleData Method for each subclass taking the specific Type of the Subclass? – Stefan Jan 05 '22 at 11:37
  • @Stefan You can't do that with virtual methods, for the reason I described. Your `Car` can define a non-virtual `GetVehicleData(Car car)` method, and you'll only be able to call it if you've got a variable of type `Car`. – canton7 Jan 05 '22 at 11:40
  • Yes this is what I meant. Thank you! – Stefan Jan 05 '22 at 12:23