#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,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,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,029 – How to Define a Constructor in a Generic Type

When you define a generic type, you include one or more type parameters in the declaration of the type, indicating the type of the type arguments that you specify when constructing an instance of the type.

As with other types, you can define one or more constructors in a generic type.  The constructor uses the name of the type, but without the associated type parameters.  You can, however, have constructors that accept parameters whose type is one of the type parameters.

In the example below, the second constructor makes use of the T type parameter.

    public class PileOf<T>
    {
        private List<T> thePile;

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

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

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

We can then use this type as follows:

            PileOf<Dog> intPile = new PileOf<Dog>(new Dog("Bowser"));
            intPile.AddThing(new Dog("Fido"));

#1,027 – Type Parameters vs. Type Arguments in 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.

Type parameters are used in the definition of the type.  In the code below, T1 and T2 are type parameters.

    public class ThingContainer<T1, T2>
    {
        private T1 thing1;
        private T2 thing2;

        public void SetThings(T1 first, T2 second)
        {
            thing1 = first;
            thing2 = second;
        }

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

You provide a type argument for each type parameter when you declare an instance of the generic type, constructing the type.  In the sample code below, string and int are the type arguments that map to the T1 and T2 type parameters.

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

#547 – Things That Can Serve as Type Parameter Constraints

A constraint on a type parameter is often a base class or interface, but can actually take on a number of different forms.

A constraint can be a class type:

    // Type parameter can be some subclass of DogToy,
    // e.g. SqueakyToy, RopeToy
    public class Dog
        where TFavThing: DogToy

Or an interface type:

    public class Dog
        where TFavThing: IBuryable

Or another type parameter:

    // TFavThing must implement IBuryable
    // TOtherThing must be castable to TFavThing's type
    public class Dog<TFavThing,TOtherThing>
        where TFavThing: IBuryable
        where TOtherThing: TFavThing

class indicates that the type must be a reference type.

    public class Dog<TFavThing>
        where TFavThing: class

struct indicates that the type must be a non-nullable value type.

    public class Dog<TFavThing>
        where TFavThing: struct

new() indicates that the type must have a public parameterless constructor defined.

public class Dog<TFavThing>
    where TFavThing: new()