#1,143 – Implement IEquatable in a Generic Type

You can use the EqualityComparer<T> type to implement IEquatable<T> for a generic type.

In the example below, we implement IEquatable<TwoThings<T1,T2>> for our TwoThings<T1,T2> type.  The EqualityComparer<T> type is used to properly compare member data, regardless of the types.  Without this, the compiler wouldn’t know how to compare two T1 or two T2 objects.

    public class TwoThings<T1,T2> : IEquatable<TwoThings<T1,T2>>
    {
        T1 thing1;
        T2 thing2;

        EqualityComparer<T1> comparer1 = EqualityComparer<T1>.Default;
        EqualityComparer<T2> comparer2 = EqualityComparer<T2>.Default;

        public TwoThings(T1 thing1, T2 thing2)
        {
            this.thing1 = thing1;
            this.thing2 = thing2;
        }

        // IEquatable
        public bool Equals(TwoThings<T1, T2> other)
        {
            return (comparer1.Equals(thing1, other.thing1) &&
                    comparer2.Equals(thing2, other.thing2));
        }
    }

Now suppose that we construct this class using types that implement value equality.  The Equals method then behaves as we’d expect.

            Dog myDog = new Dog("Jack", 5);
            TwoThings<Dog, string> first = new TwoThings<Dog, string>(myDog, "Feisty");

            Dog otherDog = new Dog("Jack", 5);
            TwoThings<Dog, string> second = new TwoThings<Dog, string>(otherDog, "Feisty");

            // Returns true
            bool eq = first.Equals(second);
Advertisements

#1,142 – Using EqualityComparer to Compare Objects in Generic Types

By default, you can’t use equality operators on objects whose type is a type argument within a generic class.  If you constrain an argument to be a reference type, you can use equality operators to do reference equality checks.  But you can‘t make use of overloaded equality functionality.

You can do a true equality check between objects using the EqualityComparer<T> class.  Below is an example.  EqualityComparer<T> makes use of overloaded equality logic in the Dog class, even though we haven’t constrained the T argument.

    public class Pile<T>
    {
        List<T> pile = new List<T>();
        EqualityComparer<T> comparer = EqualityComparer<T>.Default;

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

        public bool IsFirst(T item)
        {
            // Reference equality only, if we do
            //   where T : class
            //bool equalStd = (pile[0] == item);

            // Makes use of overloaded Equality
            // functionality in T
            bool equalBetter = comparer.Equals(pile[0], item);

            return equalBetter;
        }
    }

1142-001

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

 

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