#1,036 – Specifying Multiple Type Parameter Constraints

When you specify a constraint for a type parameter, you can specify multiple constraints for a particular type parameter.  The constraints are separated by commas.

In the example below, the type parameter T must be substituted with a type that implements both IBark and ICloneable and that also implements a parameterless constructor.

    // Type parameter T must specify a type that:
    //   Implements IBark
    //   Implements ICloneable
    //   Defines a parameterless constructor
    public class PileOf<T> where T : IBark, ICloneable, new()
    {
        private List<T> thePile = new List<T>();

        public PileOf()
        {
            // Start with default dog
            thePile.Add(new T());
        }

        public void Add(T thing)
        {
            // Store quieter version of loud dogs
            if (thing.BarkVolume > 9)
            {
                T quietGuy = (T)thing.Clone();
                quietGuy.BarkVolume = 1;
                thePile.Add(quietGuy);
            }
            else
                thePile.Add(thing);

            // Bark when added
            thePile[thePile.Count - 1].Bark("Woof");
        }
    }

#1,035 – Summary of Type Parameter Constraints

Here’s a quick summary of the different types of constraints that you can apply to type parameters:

#1,034 – Making One Type Parameter Depend on Another

When specifying a constraint for a type parameter, used in either a class or a method, you can constrain the type parameter to depend on another type parameter.  That is–the second type parameter must be identical to, or derive from, the first.

Below, the type argument used when calling the Subset method (U) must be identical to or derive from the type parameter used for the class (T).  This allows us to do a cast operation.

    public class PileOf<T>
    {
        private List<T> thePile = new List<T>();

        public void Add(T thing)
        {
            thePile.Add(thing);
        }

        public List<U> Subset<U>() where U : T
        {
            List<U> subset = new List<U>();
            foreach (T next in thePile)
            {
                if (next is U)
                    subset.Add((U)next);
            }

            return subset;
        }
    }

We could use this class as follows:

            PileOf<Animal> animals = new PileOf<Animal>();
            animals.Add(new Dog("Bowser", "Woof"));
            animals.Add(new Cat("Fluffy"));
            animals.Add(new Dog("Kirby", "Growf"));

            List<Dog> theDogs = animals.Subset<Dog>();

#1,033 – Requiring a Generic Type Parameter to Have a Parameterless Constructor

By default, when you specify a type parameter in a generic class, the type can be any .NET type.  There are times, however, when you’d like to constrain the type in some ways.

You can require that a type passed in as a type argument implement a public parameterless constructor by using the where keyword and specifying new().  The new() constraint tells the code that is constructing the type that it needs to only use types that have a parameterless constructor.

Without this constraint, you are not allowed to construct instances of the type passed in as a type parameter.  (The compiler will tell you to add the new() constraint).

With the constraint, your class might look like this:

    public class PileOf<T> where T : new()
    {
        private List<T> thePile;

        public PileOf()
        {
            thePile = new List<T>();

            // First element in list is a default T
            thePile.Add(new T());
        }
    }

#1,032 – Requiring Generic Type Parameters to Be a Reference or Value Type

By default, when you specify a type parameter in a generic class, the type can be any .NET type.  There are times, however, when you’d like to constrain the type in some ways, for example requiring that the type be either a reference type or a value type.

To require a type argument to represent a reference type, use the class keyword:

    public class PileOf<T> where T : class

To require a type argument to represent a non-nullable value type, use the struct keyword:

    public class PileOf<T> where T : struct

You may want to use one of these constraints when your generic type includes code that will only work on a reference type or on a value type.

#1,031 – Requiring Generic Type Parameters to Implement an Interface

By default, when you specify a type parameter in a generic class, the type can be any .NET type.  There are times, however, when you’d like to constrain the type in some ways, allowing only types that implement a particular interface.

You can constrain a type parameter to be of a type that implements a particular interface, using the where keyword.  In the example below, the PileOf class has a type parameter that must be a type that implements the IBark interface.  (The EverybodyBark method calls a method in IBark).

    public class PileOf<T> where T : IBark
    {
        private List<T> thePile;

        public PileOf()
        {
            thePile = new List<T>();
        }

        public void AddThing(T thing)
        {
            thePile.Add(thing);
        }

        public void EverybodyBark()
        {
            foreach (T creature in thePile)
            {
                creature.Bark();
            }
        }
    }

We can now create a PileOf<Dog>, because Dog implements IBark, but we can’t create a PileOf<Cow>.

#1,030 – Requiring Generic Type Parameters to Derive from a Specified Class

By default, when you specify a type parameter in a generic class, the type can be any .NET type.  There are times, however, when you’d like to constrain the type in some ways, allowing only certain types.

You can constrain a type parameter to be of a type that derives from (or is identical to) a particular type, using the where keyword.  In the example below, the PileOf class has a type parameter that must derive from Animal–because we look for a Habitat property.

    public class PileOf<T> where T : Animal
    {
        private List<T> thePile;
        private List<string> habitats;

        public PileOf()
        {
            thePile = new List<T>();
            habitats = new List<string>();
        }

        public void AddThing(T thing)
        {
            thePile.Add(thing);
            habitats.Add(thing.Habitat);
        }
    }

We can construct a PileOf<Dog>, but not a PileOf<int>.

            // Works
            PileOf<Dog> dogPile = new PileOf<Dog>();
            dogPile.AddThing(new Dog("Fido"));

            // Compile-time error: int can't be converted to Animal
            PileOf<int> intPile = new PileOf<int>();