#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

Advertisement

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

#1,039 – Deriving from a Generic Class

A generic class includes one or more type parameters, allowing you to later declare a variable using a constructed type by specifying type arguments for each type parameter.

If you have an existing generic class, you can define a new class that inherits from the generic class.

Assume that we have a List<T> type, i.e. we can construct the type as follows:

            List<int> someInts = new List<int>();

We can also define a new class that inherits from List<T> and makes use of its type parameter as follows:

    public class ListPlus<T> : List<T>
    {
        public void AddTwoItems(T thing1, T thing2)
        {
            base.Add(thing1);
            base.Add(thing2);
        }
    }

We can now construct the new type by providing a type argument.

            ListPlus<Dog> dogs = new ListPlus<Dog>();
            Dog fido = new Dog("Fido");
            Dog bowser = new Dog("Bowser");
            dogs.AddTwoItems(fido, bowser);

#1,038 – Type Parameter Constraints on Generic Methods

You can specify type parameter constraints for both generic types and generic methods.

The example below shows a type constraint for a generic type.

    public class MooPile<T> where T : IMoo
    {
        private List<T> mooPile = new List<T>();

        public void AddMooThing(T thing)
        {
            mooPile.Add(thing);
            mooPile[mooPile.Count - 1].Moo();
        }
    }

And below is a type constraint for a generic method (which happens to be defined within a non-generic class).

        static void SwapAndMoo<T>(ref T item1, ref T item2) where T : IMoo
        {
            T temp = item1;
            item1 = item2;
            item2 = temp;

            item1.Moo();
            item2.Moo();
        }

#1,037 – Specifying Type Parameter Constraints for More than One Type Parameter

If you define a generic type that includes more than one type parameter, you can specify type parameter constraints for each parameter independently.

In the example below, we define a generic class that accepts two type parameters.  We specify that the first type must be one that implements the IMoo interface and that the second type must implement IBark.

    public class MooBarkPile<T1,T2> where T1 : IMoo
                                   where T2 : IBark
    {
        private List<T1> mooPile = new List<T1>();
        private List<T2> barkPile = new List<T2>();

        public void AddMooThing(T1 thing)
        {
            mooPile.Add(thing);
            mooPile[mooPile.Count - 1].Moo();
        }

        public void AddBarkThing(T2 thing)
        {
            barkPile.Add(thing);
            barkPile[barkPile.Count - 1].Bark("Woof");
        }
    }

We can now use the type as follows:

            MooBarkPile<Cow,Dog> aPile = new MooBarkPile<Cow,Dog>();
            aPile.AddBarkThing(new Dog("Kirby", 10));
            aPile.AddMooThing(new Cow("Bessie"));

1037-001

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