#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.

Advertisements

#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,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);

#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>();

#324 – A Generic Class Can Have More than One Type Parameter

A generic class includes one or more type parameters that will be substituted with actual types when the class is used.  If the class has more than one type parameter, all parameters must be substituted when the class is used.

Here’s an example of a generic class that stores two objects, each having its own type.

    public class ThingContainer<TThing1, TThing2>
    {
        private TThing1 thing1;
        private TThing2 thing2;

        public void SetThings(TThing1 first, TThing2 second)
        {
            thing1 = first;
            thing2 = second;
        }

        public string DumpThings()
        {
            return string.Format("{0}, {1}", thing1.ToString(), thing2.ToString());
        }
    }

We can use this class as follows:

            ThingContainer<string,int> cont1 = new ThingContainer<string,int>();
            cont1.SetThings("Hemingway", 1899);
            Console.WriteLine(cont1.DumpThings());      //  Hemingway, 1899

            ThingContainer<Dog, DateTime> cont2 = new ThingContainer<Dog, DateTime>();
            cont2.SetThings(new Dog("Kirby", 14), new DateTime(1998, 5, 1));
            Console.WriteLine(cont2.DumpThings());      // Kirby (14 yrs), 5/1/1998 12:00:00 AM