tags:

views:

105

answers:

7

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.

+7  A: 

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.

Andrew Hare
Thanks Andrew. That really helps. Makes sense now.I was trying to figure out the difference/benefits of using constraints.
Cranialsurge
A: 

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.

Pierreten
It's more than just intent and documentation: it has practical/consequential effect.
ChrisW
Agree to disagree
Pierreten
Not much point to disagreeing to the factual matter that type constraints change the behavior of classes... even at runtime.
JasonTrue
A: 

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

Seattle Leonard
Not exactly true. The second can also contain anything that derives from car, it's just the second will not be strongly typed *to* the derived car. `List<Car>` can contain CompactCar, Sedan, Midsized, etc., but the first would actually be a list *of* CompactCar, Sedan, or Midsized.
Anthony Pegram
+4  A: 

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.

byte
Yes, and the "implements interface" constraint is far more valuable, in most cases, than the original poster's example of an "is a subclass of" constraint.
JasonTrue
+1  A: 

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();
      }
  }
} 
ChrisW
In the second case - public class CarCollection<Car>:List<Car>, you will get a compile time error because of "CarCollection<Car>" ... notice what is wrong there, you are hiding the type definition of "Car". In my example I am referring to CarCollection:List<Car> which is not the same as CarCollection<Car>:List<Car> There is no problem in spooling through the list of cars using a foreach statement.
Cranialsurge
A: 

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. :)

Matt Dotson
A: 

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.

Joel