Home > Software design >  c# inheritance - how to cast from base type to sub type?
c# inheritance - how to cast from base type to sub type?

Time:01-12

Background

I'm not sure my question in the title is actually expressed properly, but here's what I'm trying to do. (I am using a bogus analogy here to represent real code written by the team. We aren't allowed to post actual code. But I have tried to ensure that the concepts I see in our code base have been transferred over to this example)

I have some vehicle records in my database... some of them are cars, others will be trucks etc. I also have some controllers - endpoints for each vehicle type. So I have a Car controller that allows specific methods just for cars.

All of the columns in the database are identical for all types of vehicles. The vehicle DTO class looks like this:

 public class Vehicle
 {
        [Key]
        public int Id { get; set; }
        [Column]
        public int BrandName{ get; set; }
        [Column]
        public string VehicleDetails{ get; set; }

        public IDictionary<string, object> VehicleDetailsJson
        {
          get
          {
            return (IDictionary<string, object>)JsonConvert.DeserializeObject<List<ExpandoObject>>(VehicleDetails).FirstOrDefault();
          }
    }

But in the case of a car, we need additional fields. so we have this:

public class Car : Vehicle
{
  public static List<string> Models {get;} = new()
  {
     "two-seater",
     "four-seater",
     "six-seater"
  }

  public bool doSomethingToTheBrandName()
   {
      //does something to the brand string on the base class.
      return true; 
   }
}

Because all the database columns that are being return are the same, I'm using the vehicle model as a return type in my repository class. This is how I pull the data from the database: (pseudo-ish code)

    public class VehicleRepository: GenericRepository<Vehicle>, IVehicleRepository
     {
      private readonly string encryptionPassword;

     public VehicleRepository(AppSettings appSettings) : base(appSettings)
      {
        encryptionPassword = appSettings.EncryptionPassword;
      }

      public List<Vehicle> GetVehicles(string brandName= "")
      {
        var vehicles = new List<Vehicle>();

            var searchFilter = !string.IsNullOrEmpty(brand )
                               ? $"BrandName = @Brandname"
                               : "BrandName = 'all'";
            vehicles = Retrieve().ToList();
        
        return vehicles ;
    }

Here's the interface for the rpository:

public interface IVehicleRepository : IGenericRepository<Vehicle>
{
    List<Vehicle> GetVehicles(string brandName = "");
}

Problem

Where I'm running into issues is trying to call the repository from a car controller. This is where I'm not quite sure how to design / write the code.

The car controller uses requests and responses like this:

public class CarRequest: Request
{
    public string brandName{ get; set; }

}

public class CarResponse: Response
{
    public List<Car> Cars { get; set; }  //notice we are returning a car, not vehicle.
}

And here's the controller itself:

   public class CarController: ControllerBase
    {
        private readonly AppSettings appSettings;
        private readonly IVehicleRepository vehicleRepository;

    public CarResponse GetCars (CarRequest request)
    {
        try
        {
            var response = new GetCarResponse();
            response.Cars = vehicleRepository.Vehicles("ford");

        }
    }

Naturally, the system fails trying to save the response from the database call because I'm trying convert a vehicle into a car.

Questions

  • Which class should be responsible for casting from the base type "Vehicle" to "Car".
  • Also in the future, if there are Car specific methods that we need to write in a repository class, should we just do that inside the vehicle repo class?
  • Anything else that smells off?

EDIT 1

So my real code actually has an if statement baked in (I'm including the suggested refactors)

public List<T> GetVehicles<T>(string brandName= "", int someFlag)
  {
    var vehicles = new List<T>();
    if (someflag==0)
    {
         var searchFilter = !string.IsNullOrEmpty(brand )
                       ? $"BrandName = @Brandname"
                       : "BrandName = 'all'";
         vehicles = Retrieve().OfType<T>.ToList();
    }
    else
    {
       vehicles.Add(Retrieve(someFlag));
    }
    return vehicles ;
   }

I'm sorry but I'm still kinda new to OO and c#. So I wasn't sure if by TVehicle you meant T. I've seen before. But the error I'm getting on this line:

 vehicles = Retrieve().OfType<T>.ToList();

is

CS0119 'Enumerable.OfType(IEnumerable)' is a method, which is not valid in the given context

and I haven't attempted to fix the Else path but that also shows the following error:

CS1503 Argument 1: cannot convert from 'Widgets.Models.Vehicle' to 'T'

EDIT 2

   public List<TVehicle> GetVehicles<TVehicle>(string brandName= "", int someFlag)
where TVehicle: Vehicle
      {
        var vehicles = new List<TVehicle();
        if (someflag==0)
        {
             var searchFilter = !string.IsNullOrEmpty(brand )
                           ? $"BrandName = @Brandname"
                           : "BrandName = 'all'";
             vehicles = Retrieve().OfType<TVehicle>.ToList();
        }
        else
        {
           vehicles.Add(Retrieve(someFlag));
        }
        return vehicles ;
       }

But I'm still getting the same two errors on the same two lines.

CodePudding user response:

One of the solution to this is can be that, we will need to define the method generic so that we can pass the type from calling side something like:

  public List<TVehicle> GetVehicles<TVehicle>(string brandName= "")
  {
    var vehicles = new List<TVehicle>();

    var searchFilter = !string.IsNullOrEmpty(brand )
                       ? $"BrandName = @Brandname"
                       : "BrandName = 'all'";
    vehicles = Retrieve().OfType<TVehicle>.ToList();
    
    return vehicles ;
}

and now on calling side it should be something like:

var cars = GetVehicles<Car>("brand name");

CodePudding user response:

Generally it is a good practice to have a separated repo for each domain entity. So it may worth creating an abstract Vehicle repository and derive other repose from it (not tested):

public abstract class VehicleRepository<TVehicle>: GenericRepository<TVehicle>, IVehicleRepository
where TVehicle:Vehicle
     {
      private readonly string encryptionPassword;

     public VehicleRepository(AppSettings appSettings) : base(appSettings)
      {
        encryptionPassword = appSettings.EncryptionPassword;
      }

      public List<TVehicle> GetVehicles(string brandName= "")
      {
        var vehicles = new List<TVehicle>();

            var searchFilter = !string.IsNullOrEmpty(brand )
                               ? $"BrandName = @Brandname"
                               : "BrandName = 'all'";
            vehicles = Retrieve().ToList();
        
        return vehicles ;
}

and then define TruckVehicle, CarVehicle ,.. inheriting from that class.

  • Related