Daniel PP Cabral
Daniel PP Cabral

Reputation: 1624

When to use a non-generic interface as a generic type constraint

I am struggling to find a scenario where it would make sense to use a non-generic interface as a generic type constraint. Below is an arbitrary example where the non-generic method (RideCar2) is simpler than the generic method RideCar.

class Program
{
    static void Main(string[] args)
    {
        var car = new Merc();
        RideCar(car);
        RideCar2(car);
    }

    static void RideCar<T>(T t) where T : ICar
    {
        t.StartEngine();
        t.StopEngine();
    }

    static void RideCar2(ICar car)
    {
        car.StartEngine();
        car.StopEngine();
    }
}

public interface ICar
{
    void StartEngine();
    void StopEngine();
}

public class Merc : ICar
{
    public void StartEngine() { Console.WriteLine("Merc start"); }
    public void StopEngine() { Console.WriteLine("Merc stop"); }
}

It is obvious that RideCar2 is a much better implementation as it has less noise.

Is there a scenario where a non-generic interface used as a generic type constraint makes sense?

FURTHER EXAMPLES (As per the responses)

  1. Using as return type

static T RideCar(T t) where T : ICar
{
    t.StartEngine();
    t.StopEngine();
    return t;
}

Using a normal method still renders use of generic method useless, see below:


static ICar RideCar(ICar car)
{
 car.StartEngine();
 car.StopEngine();
 return car;
}
  1. Multiple interfaces

static void RideCar(T t) where T : ICar, ICanTimeTravel
{
    t.StartEngine();      // ICar
    t.TravelToYear(1955); // ICanTimeTravel
    t.StopEngine();       // ICar
}

Using a normal method with multiple paramaters still renders use of generic method useless, see below:


static void RideCar(ICar car, ICanTimeTravel canTimeTravel)
{
 car.StartEngine();
 canTimeTravel.TravelToYear(1955);
 car.StopEngine();
}

Upvotes: 1

Views: 276

Answers (2)

Gediminas Masaitis
Gediminas Masaitis

Reputation: 3212

Yes, there is. Consider:

static T RideCar<T>(T t) where T : ICar
{
    t.StartEngine();
    t.StopEngine();
    return t;
}

This will return the specific type. Now you can use the implementation specifics without having to cast it back to the implementation type, which is bad practice.

Also, you can have multiple interface constraints on the same generic argument:

static void RideCar<T>(T t) where T : ICar, ICanTimeTravel
{
    t.StartEngine();      // ICar
    t.TravelToYear(1955); // ICanTimeTravel
    t.StopEngine();       // ICar
}

Lastly, even though this is sometimes considered code smell, you can use the new() constraint along with your interface constraint, in order to create instances of your implementation type inside the method:

static T Create<T>() where T : ICar, new()
{
    T t = new T();
    return t;
}

Upvotes: 5

Michael Mairegger
Michael Mairegger

Reputation: 7301

It depends. In your case the non-generic way is preferred.

If you want to call methods that are declared on different interfaces then the generic-declaration with type-constraints makes sense:

public void Test<T>(T value, T other) 
    where T: IEquatable<T>, IComparable<T>
{
    value.Equals(other); //in IEquatable<T>
    value.CompareTo(other); //in IComparable<T>
}

But in that case I prefer using a base class if possible and use it in the following method without any type-constraints:

public class BaseCar<T> : IEquatable<T>, IComparable<T>, ICar
{
    /// [...]
}

public void Test<T>(BaseCar<T> car1, BaseCar<T> car2)
    where T: IEquatable<T>, IComparable<T>
{
    car1.Equals(car2);
    car1.CompareTo(car2);
}

or without generics but limitation to ICar:

public class BaseCar : IEquatable<ICar>, IComparable<ICar>
{
    /// [...]
}

public void Test(BaseCar car1, BaseCar car2)
{
    car1.Equals(car2);
    car2.CompareTo(car2);
}

Upvotes: 0

Related Questions