#1,141 – Reference Equality Used Even when Overload Available for Typed Parameter

If a type parameter in a generic class is constrained to be a reference type, using the class designator, then the == and != operators used for this type parameter will perform a check for reference type equality.  This is true even when the type provides a more intelligent equality check by overloading the == operator.

Assume that we overload == for the Dog type to do an intelligent equality check.

            Dog d = new Dog("Jack", 5);
            Dog d2 = new Dog("Jack", 5);

            // sameDog will be true
            bool sameDog = (d == d2);

Below, the == operator performs a reference type equality check on the type parameter.

    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)
        {
            // Reference equality, even if
            // == operator is overloaded in T
            return (pile[0] == item);
        }

Using the above class:

            Pile<Dog> pack = new Pile<Dog>();
            pack.Add(new Dog("Jack", 5));

            Dog d2 = new Dog("Jack", 5);

            // Returns false
            bool sameDog = pack.IsFirst(d2);

 

Advertisements

#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

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