#1,138 – Type Parameters in Generic Methods Can Be Constrained

You can constrain the type parameters in a generic class in a number of different ways.  You can also constrain type parameters in generic methods, using the same types of constraints.

In the example below, we define a generic method Bury<T> in the Dog class.  We constrain the type parameter, indicating that it should implement the IBuryable interface.

    public class Dog
    {
        public string Name { get; set; }

        public Dog(string name)
        {
            Name = name;
        }

        // Generic method
        public void Bury<T>(T thing) where T: IBuryable
        {
            Console.WriteLine("{0} is burying: {1}", Name, thing);
        }
    }

We can pass the Bury method any object that implements IBuryable.

            Dog d = new Dog("Bowser");
            d.Bury(new Bone());

If we try passing an object that does not implement IBuryable, we’ll get a compile-time error, indicating that the compiler can’t convert to IBuryable.

1138-001

#1,137 – Generic Methods in Non-Generic Classes

A non-generic class can contain both generic and non-generic methods.

    // Non-generic class
    public class Dog
    {
        public string Name { get; set; }

        public Dog(string name)
        {
            Name = name;
        }

        // Non-generic method
        public void Bark()
        {
            Console.WriteLine("Woof");
        }

        // Generic method
        public void Bury<T>(T thing)
        {
            Console.WriteLine("{0} is burying: {1}", Name, thing);
        }
    }

A generic class can also contain both generic and non-generic methods.

  • Generic methods are methods that introduce a type parameter not present in the generic class
  • Both generic and non-generic methods can make use of the generic class’ type parameters
    // Generic class
    public class Pile<T>
    {
        List<T> pile = new List<T>();

        // Non-generic method
        public void Add(T item)
        {
            pile.Add(item);
        }

        // Generic method (introduces T2)
        public void AddWithOtherThing<T2>(T item, T2 otherThing)
        {
            this.Add(T);
            Console.WriteLine("Other thing is {0}", otherThing);
        }
    }

#1,135 – Overloading a Generic Class

generic class is a class that takes one or more type parameters, which it then uses in the definition of the class.  It can be thought of as a template for a class.

    public class Pile<T>

A generic class can have more than one type parameter.

    public class DoublePile<T1, T2>

You can also declare two different generic classes having the same name, differing only in the number of their type parameters.  The two classes will be treated as two completely different classes.

    public class Pile<T>
    {
    }

    public class Pile<T1, T2>
    {
    }

The appropriate class will be used based on the number of arguments provided when the type is constructed.

            Pile<int> someInts = new Pile<int>();
            Pile<string, double> namedDoubles = new Pile<string, double>();

#1,134 – Use “of” Terminology for Generics

It’s often the convention to use the word “of” in reading the name of a generic type, i.e. “[class] of [type]”.  For example, suppose that you define the following type:

public class Pile<T>
{
   // stuff here
}

You’d typically refer to this generic type as “Pile of T”.

Suppose that you then declared an instance of the constructed type as follows:

Pile<Dog> dogPile = new Pile<Dog>();

You’d then refer to this constructed type as “Pile of Dog”.

#1,070 – A Generic Type Can Be Compiled

Recall that a generic type is a template for a class that you can define later.  The generic type contains type parameters that act as placeholders for type arguments that you supply when you define a constructed type based on the generic type.  Without defining the constructed type, the type parameters in the generic type have no assigned type.  That is, they can be of any type (unless some constraints have been specified for the type parameter).

You can compile a particular generic type without have any constructed types that make use of the generic type.  For example, List<T> is compiled and included in mscorlib.dll.  This allows you to later use the generic type in your own code.

#1,044 – How Static Data Behaves in Generic Types

There is only a single copy of each static data item, rather than one copy for each instance of the class.

When you have a static data item in a generic class, there is a single copy of that data item for each constructed type based on the generic type.  A constructed type is a type declaration that is based on a generic type, providing arguments for the generic type’s type parameters.

Assume that we define a PileOf<T> type that has a static NumInAllPiles field, incremented as part of an Add method.  We reference the static field using the constructed class name and see that each constructed type has its own copy of the static data.

            // Pile of 1 dog
            PileOf<Dog> pile1 = new PileOf<Dog>();
            pile1.Add(new Dog("Bowser"));

            // Pile of 2 dogs
            PileOf<Dog> pile2 = new PileOf<Dog>();
            pile2.Add(new Dog("Kirby"));
            pile2.Add(new Dog("Fido"));

            // Pile of 1 cat
            PileOf<Cat> pile3 = new PileOf<Cat>();
            pile3.Add(new Cat("Fluffy"));

            Console.WriteLine(PileOf<Dog>.NumInAllPiles);  // 3
            Console.WriteLine(PileOf<Cat>.NumInAllPiles);  // 1

#1,043 – Deriving from a Self-Referencing Constructed Type, part II

When defining a type, you can derive from a constructed generic type that takes the defining type as its type argument.  This technique allows extending the behavior of some base class, but with the advantages of using generics.

    public class CreaturesParents<T>
    {
        public T Mom { get; set; }
        public T Dad { get; set; }

        public string ParentInfo()
        {
            return string.Format("Mom: {0}, Dad: {1}", Mom.ToString(), Dad.ToString());
        }
    }

    public class Dog : CreaturesParents<Dog>
    {
        public string Name { get; set; }
        public int Age { get; set; }

        public Dog(string name, int age)
        {
            Name = name;
            Age = age;
        }

        public override string ToString()
        {
            return string.Format("{0} is {1} yrs old", Name, Age);
        }
    }

Example of using this type:

            Dog fido = new Dog("Fido", 4);
            Dog lassie = new Dog("Lassie", 12);
            Dog bowser = new Dog("Bowser", 9);

            fido.Mom = lassie;
            fido.Dad = bowser;

            Console.WriteLine(fido.ParentInfo());

1043-001

#1,042 – Deriving from a Self-Referencing Constructed Type, Part I

When defining a type, the type can derive from a constructed generic type.

public class DogList : List<string>

You can use the name of the type being defined as the type argument to the type (or interface) that you’re deriving from.

Below, the Dog type is an argument to the IComparable generic interface.  We’re saying that we want Dog to implement IComparable<T> and it makes sense that the argument should be Dog.

    public class Dog : IComparable<Dog>
    {
        public string Name { get; set; }
        public int WoofPower { get; set; }

        public Dog(string name, int woofPower)
        {
            Name = name;
            WoofPower = woofPower;
        }

        public int CompareTo(Dog other)
        {
            // Returns
            //   < 0 if this < other
            //   0 is this == other
            //     > 0 if this > other
            return this.WoofPower - other.WoofPower;
        }
    }

Using Dog:

            Dog fido = new Dog("Fido", 4);
            Dog bowser = new Dog("Bowser", 9);

            int compare = fido.CompareTo(bowser);

#1,041 – Adding New Type Parameters when You Derive from a Generic Class

You can define a generic class that inherits from another generic class, for example:

public class ListPlus<T> : List<T>

You can also add new type parameters in the new class being defined.  In the example below, ListPlus derives from List<T> and adds a second type parameter.

        public class ListPlus<T, TShadow> : List<T>
        {
            private List<TShadow> shadowList = new List<TShadow>();

            public void AddItem(T mainItem, TShadow shadowItem)
            {
                base.Add(mainItem);
                shadowList.Add(shadowItem);
            }
        }

When we construct the type, we need to provide type arguments for both type parameters.

            ListPlus<Dog, Cat> myListPlus = new ListPlus<Dog, Cat>();
            Dog fido = new Dog("Fido");
            Cat puff = new Cat("Puff");
            myListPlus.AddItem(fido, puff);

#1,040 – Deriving from a Constructed Type

You can define a generic class that inherits from another generic class, for example:

public class ListPlus<T> : List<T>

You can also derive from a constructed type, rather than a generic type.  A constructed type is just a generic type with the type parameters filled in (type arguments provided).

For example:

    public class DogListPlus : List<Dog>
    {
        public void AddTwoDogs(Dog d1, Dog d2)
        {
            base.Add(d1);
            base.Add(d2);
        }
    }

We can then use this new type as follows:

            DogListPlus dogList = new DogListPlus();
            Dog fido = new Dog("Fido");
            Dog bowser = new Dog("Bowser");
            dogList.AddTwoDogs(fido, bowser);