I have a class CarInfo
that holds information of a car and its owner. The owner can be either a known customer (i.e. we can look it up and populate a Customer
class) or we only know the name of the customer (in which case we cannot populate a Customer class). I have a Blazor form that exposes the CarInfo class. How do I design this class in respect to the customer info member, which can be either Customer
or just First- and last name?
public CarInfo
{
public Guid Id { get; }
...
public Customer? { get; set; } // Either this member can be populated
public string? FirstName { get; set; } // or the First- and Last name members.
public string? LastName { get; set; }
...
}
What is the best design to handle this? Should I break out Customer and First- and Last name into a new class, e.g. CustomerInfo
and handle this polymorphism there?
public CustomerInfo
{
public Customer? { get; private set; }
public string? FirstName { get; private set; }
public string? LastName { get; private set; }
public CustomerInfo(Customer customer) => Customer = customer;
public CustomerInfo(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
}
}
and use it like this?
public CarInfo
{
public Guid Id { get; }
...
public CustomerInfo { get; set; }
...
}
I'm a little stuck in what best practises or patterns should be referred to here.
At some point in the future, we may be getting "Discriminated Unions" added to C#.
These allow you to declare a type which may only contain an item with one of a specified set of types. In your case, the only types allowed are Customer
and what I'm going to call CustomerName
types.
Until that time, you can use the OneOf
NuGet package to do this.
Your CarInfo
and supporting types might look something like this:
public class CarInfo
{
public Guid Id { get; } = Guid.NewGuid();
public OneOf<Customer, CustomerName> CustomerDetails { get; set; }
}
public record CustomerName(string FirstName, string LastName)
{
}
public record Customer(string FirstName, string LastName, string Etc /*Other stuff*/)
{
}
Then you can initialise CarInfo.CustomerDetails
with either a CustomerName
or a Customer
but nothing else:
public static class Program
{
public static void Main()
{
var carInfo1 = new CarInfo
{
CustomerDetails = new Customer("Fred", "Bloggs", "Address")
};
var carInfo2 = new CarInfo
{
CustomerDetails = new CustomerName("Fred", "Bloggs")
};
}
}
When you access the CustomerDetails
property you would generally have to account for all the types that the property might have - this avoids forgetting to deal with them all:
carInfo1.CustomerDetails.Switch(
customer => Console.WriteLine($"Full customer available: {customer}"),
customerName => Console.WriteLine($"Only name and address available: {customerName}")
);
carInfo2.CustomerDetails.Switch(
customer => Console.WriteLine($"Full customer available: {customer}"),
customerName => Console.WriteLine($"Only name and address available: {customerName}")
);
Note that using OneOf<>
may not be the best model for your situation, but it does fit with your description. You should, however, consider other possibilities such as using nullable properties for the missing information.