#1,140 – Comparing Reference-Typed Objects in Generic Types

You can’t normally use the == or != operators on objects whose type is one of the type parameters in a generic class.  The compiler doesn’t know enough about the type to be able to use the equality or inequality operators.

If you add a class constraint on a type parameter, indicating that it must be a reference type, then you can use the == and != operators to do basic reference type equality.

    public class Pile<T> where T : class
    {
        List<T> pile = new List<T>();

        public void Add(T item)
        {
            if (!pile.Contains(item))
                pile.Add(item);
        }

        public bool IsFirst(T item)
        {
            return (pile[0] == item);
        }
    }

Our IsFirst method returns true if the item parameter is the same object as the first item in the collection.

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

            Dog d1 = new Dog("Bowser");
            Dog d1B = new Dog("Bowser");

            pack.Add(d1);

            Console.WriteLine(pack.IsFirst(d1));
            Console.WriteLine(pack.IsFirst(d1B));

1140-001

Advertisements

#1,139 – The Problem with Comparisons of Objects in Generic Types

If you don’t constrain a type parameter in a generic class, the compiler will not let you compare two instances of objects of that type using the == or != operators.

In the example below, we store a collection of objects whose type is the type parameter T.  

    public class Pile<T>
    {
        List<T> pile = new List<T>();

        public void Add(T item)
        {
            if (!pile.Contains(item))
                pile.Add(item);
        }

        public bool IsFirst(T item)
        {
            // Compare to null allowed
            bool isnull = (item == null);

            return (pile[0] == item) ;
        }

        public void Dump()
        {
            foreach (T item in pile)
                Console.WriteLine(item);
        }
    }

If we try compiling this code, we’ll get a compile-time error in the IsFirst method, indicating that we can’t apply the == operator.  The compiler doesn’t have enough information about the type T to know that we can use the == operator.
1139-002

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