开发者

What is the advantage of using constraints on C# Generics

Can someone tell me what the difference is between the following

public class CarCollection<T>:List<T> where T:Car{}

and

public class CarCollection:List<Car>{}

To me they seem to do the开发者_开发知识库 same thing, create type-safe collection of "Car" objects.


Constraints are nice when you need some information about the generic type argument. By constraining the type you are not only limiting the set of valid type arguments but you are also giving yourself the ability to make compile-time decisions based on the common elements of that set of types.

In your specific example the generic constraint does not provide any benefit but in other circumstances they can. Consider this example:

T foo<T>() 
{
    return null;
}

This method cannot compile because the compiler knows (as well I should have when I wrote the method) that the generic type argument could possibly be a value type which cannot be set to null. By adding a constraint I can make this code compile:

T foo<T>() 
    where T : class
{
    return null;
}

I have now limited the valid set of generic type arguments to just classes. Since the compiler now knows that T must be a class it allows me to return null since there are no scenarios in which null could not be returned. While this is not a terribly useful example I am sure you can extrapolate ways that this can be helpful.


If you implement a generic that will perform comparisons, the where clause is necessary. For example:

public class Foo<T> where T : IComparable<T>
{
    public static void Bar(T blah, T bloo)
    {
        if(blah.CompareTo(bloo) < 0)    //needs the constraint
            Console.WriteLine("blee!");
    }
}

This is a specific example, but it illustrates the concept that your where clause identifies that your generic type will adhere to a type, and thus is able to utilize functionality.


I think that the second format is misleading: the "Car" is a template parameter, not a reference to the "Car" class which you presumably defined elsewere.

The first format lets you invoke members (methods and properties) of the Car class on instances of T within the template class.

class Car
{
  public void drive() {}
}

public class CarCollection<T>:List<T> where T:Car
{
  List<T> list;

  void driveCars()
  {
    foreach (T car in list)
    {
      //know that T is Car
      car.drive();
    }
  }
} 

public class CarCollection<Car>:List<Car>
{
  List<Car> list;

  void driveCars()
  {
    foreach (Car car in list)
    {
      //compiler error: no relation between the 'Car' template parameter
      //and the 'Car' class
      car.drive();
      }
  }
} 


The top one creates a typed collection of Car or anything that derives from Car like SportsCar.

public class SportsCar : Car
{
 bool Turbo { get; set;}
}

On the top, I could create a collection of sportscars

var sports = new CarCollection<SportsCar>();
sports.Add(new SportsCar());
...
sports.First().Turbo = true;

On the bottom I'd have

var cars = new CarCollection<Car>();
cars.Add(new SportsCar());
((SportsCar)cars.First()).Turbo = true;

It's similar to having a collection of objects vs a collection of strings. :)


It's all about showing intent. If the user of your class knows what constraints are put on a type parameter, they can design their own code with higher fidelity.

Plus your code becomes more self documenting.


The first example is much more flexible. You can use it with a list of any kind of object as long as T is Car

The second example says it has to be a car. You could not use the second with a CompactCar or Truck. That is if CompactCar or Truck derived from Car


Having gone through all the posts, I think one of the most valuable uses has not been discussed (unless I missed it).

You can specify that T has to implement more than one interface. Say you want it to be ISomething, ISomethingElse, and IOneOtherThing. You can specify those three specific interfaces.

static void DoSomething<T>(T myobj)
    where T : ISomething, ISomethingElse, IOneOtherThing
{
    myobj.Something();
    myobj.SomethingElse();
    myobj.OneOtherThing();
}

A case may be where you need to be able to compare (IComparable) two generic types and return a clone (IClonable) of one. Or something like that.

I'm trying to think of a clean and reusable way to accomplish this same thing without this feature.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜