I'm wondering if there is a way to constrain the implementations of a generic type by asking Not to implement a specific interface
Something like
public class PrivateGarage<TVehicle> where TVehicle : **not** Itruck
{
...
}
This might works but it's less elegant
public class PrivateGarage<TVehicle>
{
public PrivateGarage()
{
if(typeof(TVehicle) is Itruck)
{
throw new ArgumentException("Truck is not a valid type for private garage");
}
}
}
CodePudding user response:
No, there isn't. The only way to specify constraints is inclusive. There is no way to exclude specific subtypes. See the documentation for the list of permitted types of constraints.
The reason, most likely, is such a constraint would break polymorphism. If it were possible, it would mean that instances of a specific descendant of the actual type parameter, and its all descendants would, could not be passed to the generic class.
A possible alternate way to impose such a constraint is to introduce properties at an IVehicle
interface such as:
public interface IVehicle
{
bool CanCarryPassengers { get; }
bool CanCarryCargo { get; }
}
However, there's much more to check for a hypothetical PrivateGarage
, so in the reality, the conditional to allow a particular vehicle in the garage would be much more complicated than a simple negative constraint.
CodePudding user response:
No, there is no weay to exclude a type, constraints don't work that way.
A common solution would be to have an interface specifically for this, like IVehicleThatCanGoInGarage
, which may itself also implement IVehicle
public interface IVehicleThatCanGoInGarage : IVehicle
{}
public class PrivateGarage<TVehicle> where TVehicle : IVehicleThatCanGoInGarage
{
...
}
CodePudding user response:
There is an approach you can take to solve this, but it only works well if there is only one discriminant to worry about.
Let's suppose the base interface is IVehicle
:
public interface IVehicle
{
public void Park();
}
In your case, the discriminant is whether or not the vehicle can go in the garage, i.e., is it a "private vehicle"?
The following two interfaces can represent the discriminant:
public interface ICommercialVehicle: IVehicle
{
}
public interface IPrivateVehicle: IVehicle
{
}
Now you can represent a private garage by requiring IPrivateVehicle
rather than IVehicle
:
public class PrivateGarage<T> where T: IPrivateVehicle
{
readonly List<IPrivateVehicle> _vehicles = new();
public void Park(T vehicle)
{
_vehicles.Add(vehicle);
}
}
Suppose you also have a Truck
type that does not inherit directly or indirectly from IPrivateVehicle
. In that case if you try to create a PrivateGarage<Truck>
you'll get a compile error.
This approach does not scale at all well, so it's probably better to take the approach of using properties and checking them at runtime.
A slightly different approach is to use interfaces as "Tags". I must stress that this is considered by many to be a bad design.
To do that you'd use an IPrivateVehicle
tag as a kind of attribute.
Then the hierarchy would be like this:
public interface IVehicle
{
public void Park();
}
public interface IPrivateVehicle {} // Tag interface. Considered bad design.
public class PrivateGarage<T> where T: IPrivateVehicle, IVehicle
{
readonly List<IVehicle> _vehicles = new();
public void Park(T vehicle)
{
_vehicles.Add(vehicle);
}
}
public interface ICar: IVehicle, IPrivateVehicle
{
}
public interface ITruck : IVehicle
{
}
Then if you had concrete classes implementing ICar
and ITruck
called Car
and Truck
respectively:
var carGarage = new PrivateGarage<Car>(); // Compiles
var truckGarage = new PrivateGarage<Truck>(); // Does not compile.
- An advantage of this approach is that you can use many tags.
- An disadvantage of this approach is that you can use many tags. ;)