Fundamentals - Interfaces

Summary

Interface When to use

ICloneable

Allows a class to perform deep-copying of its instances.

IConvertible

Defines a series of ToX methods (such as ToInt, ToDouble, ToDateTime, etc.) that convert the value of the implementing reference or value type to an equivalent CLR type.
IDispoable Allows a class to properly dispose of its managed and unmanaged resources.
IServiceProvider Part of the Service Locator pattern, this interfaces defines a mechanism for retrieving a service (business) object.
IComparable & IComparer This interfaces should be implemented by a class when its instances must be sortable.
IComparable<T> & IComparer<T> This interfaces should be implemented by a class when its instances must be sortable. Prefer these to the non-generic version as these provide type safety.
IEquatable<T>

Allows you to determine if an instance is equal to another. Has the same functionality as Object.Equals except that this interface is type-safe and performs better. Prefer this to Object.Equals.

IEqualityComparer & IEqualityComparer<T>

These interfaces should be implemented by a class when that class will be used by a collection that accepts an IEqualityComparer (i.e., Dictionary). This interface allows an object to be compared for equality against another.

IObserver<T> and IObservable<T> The IObserver<T> and IObservable<T> interfaces provide a generalized mechanism for push-based notification, also known as the observer design pattern, or publisher-subscriber:

ISet<T>

ISet<T> interface provides methods for implementing sets, which are collections that have unique elements and specific operations.

ICloneable

Recall that Object.MemberwiseClone creates a shallow copy of an object as follows: MemberwiseClone creates a new object and then copies the non-static fields of the source object to the new object. If a field is a value type, a bit-by-bit copy is performed. If a field is a reference type, the reference (address) is copied and the newly copied field continues to point to the source memory location. To allow a class to perform deep-copies, implement ICloneable.

ICloneable defines a single method named Clone. Clone can either be implemented as a shallow copy (similar to that of MemberwiseClone), or a deep copy. In a deep copy, all objects are duplicated,  whereas in a shallow copy, only top-level objects are duplicated and the lower levels contain references.

Finally, note that IClonebale.Clone can be used to simulate a C++ copy constructor functionality, but there is no way to implicitly invoke Clone. One way to implicitly invoke Clone would have been by overloading the equality operator, but operator= cannot be overloaded in C#.

// This class will be contained inside a Student class.
class Address
{
    private string strStreet;
    private int nHouseNumber;

    public string Street
    {
        get { return strStreet; }
        set { strStreet = value; }
    }
    public int HouseNumber
    {
        get { return nHouseNumber; }
        set { nHouseNumber = value; }
    }
}

// This is the top-level class that is cloneable
class Student : System.ICloneable
{
    private int m_nID;
    private Address m_obAddress;

    public Student(int id, string street, int houseno)
    {
        m_obAddress = new Address();
        m_obAddress.HouseNumber = houseno;
        m_obAddress.Street = street;
        m_nID = id;
    }

    public Address Address
    {
        get { return m_obAddress; }
    }
    public int ID
    {
        get { return m_nID; }
    }

    #region ICloneable Members
    // Clone can be implemented either as shallow copy or deep copy. Here it performs a deep copy
    // as it creates and initializes a totally new object based on the current object
    public object Clone()
    {
        Student s = new Student(ID, Address.Street, Address.HouseNumber);
        return s;
    }
    #endregion
}

private static void TestICloneable()
{
    // Create an object then clone it
    Student s1 = new Student(1, "main street", 123);
    Student s2 = (Student)s1.Clone();

    // Check if objects are the same instnaces. We can use object.ReferneceEquals
    // and object.Equals (object.Equals was not overridden and hence uses reference
    // equality)


    // Top-level object
    bool bSameInstance1 = Student.ReferenceEquals(s1, s2);                 // false
    bool bSameInstance2 = s1.Equals(s2);                                   // false

    // Lower-level object
    bool bSameInstance3 = object.ReferenceEquals(s1.Address, s2.Address);  // false
    bool bSameInstance4 = s1.Address.Equals(s2.Address);                   // false
}

IConvertible

Defines a series of ToX methods (such as ToInt, ToDouble, ToDateTime, etc.) that convert the value of the implementing reference or value type to an equivalent CLR type. If there is no meaningful conversion to a CLR type, then the particular interface method implementation should throw an InvalidCastException exception.

Note that all value types inherit from IConvertible, but none of them publicly expose IConvertible members! This is because these types implement IConvertible using explicit interface member implementation. Therefore, IConvertible interface implementation can be invoked explicitly, but most often it is invoked implicitly via the System.Convert class. The following example illustrates:

class ConvertiblePerson : System.IConvertible
{
    // Member variables
    private string m_strName;
    private DateTime m_dtDOB;

    // Constructors
   
public ConvertiblePerson(string name, DateTime dob)
    {
        m_strName = name;
        m_dtDOB = dob;
    }

    // Properties
   
public string Name  { get { return m_strName; } }
    public DateTime DOB { get { return m_dtDOB; } }


    #region IConvertible Members
    DateTime IConvertible.ToDateTime(IFormatProvider provider)
    {
        return DOB;
    }

    string IConvertible.ToString(IFormatProvider provider)
    {
        return Name;
    }

    TypeCode IConvertible.GetTypeCode()
    {
        return TypeCode.Object;
    }


    bool IConvertible.ToBoolean(IFormatProvider provider)
    {
        throw new InvalidCastException("The method or operation is not implemented.");
    }

    byte IConvertible.ToByte(IFormatProvider provider)
    {
        throw new InvalidCastException("The method or operation is not implemented.");
    }

    char IConvertible.ToChar(IFormatProvider provider)
    {
        throw new InvalidCastException("The method or operation is not implemented.");
    }

    decimal IConvertible.ToDecimal(IFormatProvider provider)
    {
        throw new Exception("The method or operation is not implemented.");
    }

    double IConvertible.ToDouble(IFormatProvider provider)
    {
        throw new Exception("The method or operation is not implemented.");
    }

    short IConvertible.ToInt16(IFormatProvider provider)
    {
        throw new Exception("The method or operation is not implemented.");
    }

    int IConvertible.ToInt32(IFormatProvider provider)
    {
        throw new Exception("The method or operation is not implemented.");
    }

    long IConvertible.ToInt64(IFormatProvider provider)
    {
        throw new Exception("The method or operation is not implemented.");
    }

    sbyte IConvertible.ToSByte(IFormatProvider provider)
    {
        throw new Exception("The method or operation is not implemented.");
    }

    float IConvertible.ToSingle(IFormatProvider provider)
    {
        throw new Exception("The method or operation is not implemented.");
    }

    object IConvertible.ToType(Type conversionType, IFormatProvider provider)
    {
        throw new Exception("The method or operation is not implemented.");
    }

    ushort IConvertible.ToUInt16(IFormatProvider provider)
    {
        throw new Exception("The method or operation is not implemented.");
    }

    uint IConvertible.ToUInt32(IFormatProvider provider)
    {
        throw new Exception("The method or operation is not implemented.");
    }

    ulong IConvertible.ToUInt64(IFormatProvider provider)
    {
        throw new Exception("The method or operation is not implemented.");
    }
    #endregion
}

private static void TestIConvertible()
{
    try
    {
        // Note that ob does not expose IConvertible interface members since they have been
        // implementes using explicit interface implementatin
   
    ConvertiblePerson ob = new ConvertiblePerson( "yazan", DateTime.Parse( "01/01/2000" ));

        // The following calls IConvertible.ToString(IFormatProvider provider) with null
        // for 'provider' parameter
   
    string s1 = Convert.ToString(ob);          // yazan

        // The following calls IConvertible.ToDateTime(IFormatProvider provider) with null
        // for 'provider' parameter
   
    DateTime dt1 = Convert.ToDateTime(ob);     // {01/01/2000 00:00:00}

        // These two calls are equivalent to those above with the same results
   
    string s2 = ((IConvertible)ob).ToString(null);         // yazan
   
    DateTime dt2 = ((IConvertible)ob).ToDateTime(null);    // {01/01/2000 00:00:00}

        // Note that all value types derive from IConvertable. Conver.ToX() calls the appropriate
        // IConvertible implementation
   
    int i = 10;
        double d1 = ((IConvertible)i).ToDouble(null);     // 10.0
   
    double d2 = Convert.ToDouble(i);                  // 10.0
    }
    catch (Exception ex)
    {
        Trace.WriteLine( ex.Message );
    }
}

IDispoable

The interface has been fully described in Fundamentals - System.Object under the Finalize section.

IServiceProvider

This interface is usually part of the Service Locator design pattern. This pattern provides a centralized point of control by centralizing distributed object lookups. In other words, this pattern provides an object that knows how to get hold of all the services that an application might need.

This pattern (a J2EE pattern) consists mainly of a Service Locator, a Service Factory, and a client. The Service Locator abstracts the API lookup services and is often implemented as a Singleton. The Service Factory represents an internal, but central object used by the Service Locator, to provide life cycle management for business objects. The client, which is any object that requires access to a business object, uses the Service Locator to query, create and return the requested service. For example:

CustomerManager obCustomerManager = ServiceLocator.GetInstance().GetService( typeof(CustomerManager) );

ServiceLocator implements IServiceProvider.GetService and takes a Type instance to lookup (possible from app.config) all required information for that type such as implementing assembly, namespace, and host name (if object is remote). GetService then supplies this information to the Service Factory which takes care of creating the actual object. Based on the information received from Service Locator, the Service Factory may create the object using new, Activator.GetObject, etc. The object is then returned to the client. The client does not  and should not care how GetService created the CustomerManager object

 

IComparable & IComparer

IComparable can be implemented by a value type or a reference type to create type-specific comparison for purposes such as sorting. This interface is often implemented by types whose values can be ordered. This interface finds its main use in Array.Sort( myArray ) method which sorts the elements of the array myArray using the IComparable implementation of each element in the array.

IComparer on the other hand, is not implemented on a type directly, but rather it is implemented on helper objects, typically to provide multiple sort orders. Again, this interface finds its main use in Array.Sort() methods such as Array.Sort(myArray, myComparerImpl) which sorts the elements of the array myArray using the supplied IComparer implementation.

The following example illustrates:

// First Employee implementation impelments IComparable directly. Also has a couple
// of nested classes that implement IComparere to proivde extra comparing functionality
class Employee : System.IComparable
{
    // Data members
    private int nID;
    private DateTime dtDOB;

    // Constructors
   
public Employee(int n, DateTime dt)
    {
        nID = n;
        dtDOB = dt;
    }

    // Properties
    public int ID { get { return nID; } }
    public DateTime DOB { get { return dtDOB; } }

    // IComparable interface. This performs the default comparison since this object derives
    // directly from IComparable. The comparison is performed on the ID field. Also note
    // how comparison is being performed (nID < e.nID and not e.nID < nID).
    // The parameter oCompared must be of the same type as the type that implements IComparable.
    // By definition, an object compares greater than a null, and two null references
    // compare equal to each other.

    public int CompareTo(object oCompared)
    {
        if (this.GetType() != oCompared.GetType())
            throw new InvalidOperationException("Objects being compared have different types");
        Employee e = (Employee)oCompared;

        // Do actual comparison
        if (nID == e.nID)
            return 0;
        else if (nID < e.nID)
            return -1;
        else
            return 1;
    }

    // IComaprer class: This is a custom comparer that compares DOBs in an ascending order
    // Note how IComparer is implemented on a helper class to provide custom sorting functioanlity

    private class AscEmployeeDOB : System.Collections.IComparer
    {
        public int Compare(object lhs, object rhs)
        {
            if (lhs.GetType() != rhs.GetType())
                throw new InvalidOperationException("Objects being compared have different types");

            Employee eLHS = (Employee)lhs;
            Employee eRHS = (Employee)rhs;

            if (eLHS.DOB == eRHS.DOB)
                return 0;
            else if (eLHS.DOB > eRHS.DOB)
                return 1;
            else
                return -1;
        }
    }

    // IComaprer class: This is a custom comparer that compares DOBs in an descending order
    // Note how IComparer is implemented on a helper class to provide custom sorting functioanlity

    private class DescEmployeeDOB : System.Collections.IComparer
    {
        public int Compare(object lhs, object rhs)
        {
            if (lhs.GetType() != rhs.GetType())
                throw new InvalidOperationException("Objects being compared have different types");

            Employee eLHS = (Employee)lhs;
            Employee eRHS = (Employee)rhs;

            if (eLHS.DOB == eRHS.DOB)
                return 0;
            else if (eLHS.DOB < eRHS.DOB)
                return 1;
            else
                return -1;
        }
    }

    // Helper classes to get appropriate comparer implementation
    public static System.Collections.IComparer GetAscEmployeeDOB()
    {
        System.Collections.IComparer ob = new AscEmployeeDOB();
        return ob;
    }

    public static System.Collections.IComparer GetDescEmployeeDOB()
    {
        System.Collections.IComparer ob = new DescEmployeeDOB();
        return ob;
    }
}

 private static void TestIComparable()
{
    // Create an array of employees
    Employee[] aEmployees = new Employee[4];
    aEmployees[0] = new Employee(4, DateTime.Parse("1/1/2004"));
    aEmployees[1] = new Employee(2, DateTime.Parse("1/1/2002"));
    aEmployees[2] = new Employee(1, DateTime.Parse("1/1/2001"));
    aEmployees[3] = new Employee(3, DateTime.Parse("1/1/2003"));

    // Now sort using the default sort (IComparable) which sorts on the id
    Array.Sort(aEmployees);
    foreach (Employee e in aEmployees)
        Trace.WriteLine(e.ID);

    // Sort using IComparer
    Array.Sort(aEmployees, Employee.GetAscEmployeeDOB());
    foreach (Employee e in aEmployees)
        Trace.WriteLine(e.DOB);

    Array.Sort(aEmployees, Employee.GetDescEmployeeDOB());
    foreach (Employee e in aEmployees)
        Trace.WriteLine(e.DOB);
}

The following shows an alternative implementation except that IComparable is now implemented by a base class that should be inherited for all objects that are meant to be compared:

public abstract class ComparableObject : System.IComparable
{
    // IComparable method (mapped to an abstract method!)
    public abstract int CompareTo(object oCompared);

    // Helper method to do pre-comparison checks
    private int InternalCompare(object oCompared)
    {
        // Check that both objects have the same run-time type (note use
        // of GetType which gets run-time type as opposed to typeof which
        // gets the static type. If objects are of different types, the comparison
        // will be based on type names

        if (this.GetType() == oCompared.GetType())
            return CompareTo(oCompared);
        else
            return this.GetType().FullName.CompareTo(oCompared.GetType().FullName);
    }

    // Comparison operators
   
public static bool operator ==(ComparableObject obLHS, object obRHS)
    {
        return (obLHS.InternalCompare(obRHS) == 0);
    }
    public static bool operator !=(ComparableObject obLHS, object obRHS)
    {
        return (obLHS.InternalCompare(obRHS) != 0);
    }
    public static bool operator <(ComparableObject obLHS, object obRHS)
    {
        return (obLHS.InternalCompare(obRHS) < 0);
    }
    public static bool operator >(ComparableObject obLHS, object obRHS)
    {
        return (obLHS.InternalCompare(obRHS) > 0);
    }
    public static bool operator <=(ComparableObject obLHS, object obRHS)
    {
        return (obLHS.InternalCompare(obRHS) <= 0);
    }
    public static bool operator >=(ComparableObject obLHS, object obRHS)
    {
        return (obLHS.InternalCompare(obRHS) >= 0);
    }
    public override bool Equals(object obj)
    {
        return (InternalCompare(obj) == 0);
    }
}

class Employee2 : BaseStructures.Basic.ComparableObject
{
    // Data members
   
private int nID;
    private DateTime dtDOB;

    // Constructors
    public Employee2(int n, DateTime dt)
    {
        nID = n;
        dtDOB = dt;
    }

    // Properties
    public int ID
    {
        get { return nID; }
    }

    public DateTime DOB
    {
        get { return dtDOB; }
    }

    // IComparable interface. This performs the default comparison since this object derives
    // directly from IComparable. The comparison is performed on the ID field. Also note
    // how comparison is being performed (nID < e.nID and not e.nID < nID).
    // The parameter oCompared must be of the same type as the type that implements IComparable.

    public override int CompareTo(object oCompared)
    {
        if (this.GetType() != oCompared.GetType())
            throw new InvalidOperationException("Objects being compared have different types");
        Employee2 e = (Employee2)oCompared;

        // Do actual comparison
        if (nID == e.nID)
            return 0;
        else if (nID < e.nID)
            return -1;
        else
            return 1;
    }

    // IComaprer class: This is a custom comparer that compares DOBs in an ascending order
    private class AscEmployeeDOB : IComparer
    {
        public int Compare(object lhs, object rhs)
        {
            if (lhs.GetType() != rhs.GetType())
                throw new InvalidOperationException("Objects being compared have different types");

            Employee2 eLHS = (Employee2)lhs;
            Employee2 eRHS = (Employee2)rhs;

            if (eLHS.DOB == eRHS.DOB)
                return 0;
            else if (eLHS.DOB > eRHS.DOB)
                return 1;
            else
                return -1;
        }
    }

    // IComaprer class: This is a custom comparer that compares DOBs in an descending order
    private class DescEmployeeDOB : IComparer
    {
        public int Compare(object lhs, object rhs)
        {
            if (lhs.GetType() != rhs.GetType())
                throw new InvalidOperationException("Objects being compared have different types");

            Employee2 eLHS = (Employee2)lhs;
            Employee2 eRHS = (Employee2)rhs;

            if (eLHS.DOB == eRHS.DOB)
                return 0;
            else if (eLHS.DOB < eRHS.DOB)
                return 1;
            else
                return -1;
        }
    }

    // Helper classes to get appropriate comparer implementation
    public static System.Collections.IComparer GetAscEmployeeDOB()
    {
        System.Collections.IComparer ob = new AscEmployeeDOB();
        return ob;
    }

    public static System.Collections.IComparer GetDescEmployeeDOB()
    {
        System.Collections.IComparer ob = new DescEmployeeDOB();
        return ob;
    }   
}

private static void TestIComparable2()
{
    // Create an array of employees
    Employee2[] aEmployees = new Employee2[4];
    aEmployees[0] = new Employee2(4, DateTime.Parse("1/1/2001"));
    aEmployees[1] = new Employee2(2, DateTime.Parse("1/1/2003"));
    aEmployees[2] = new Employee2(1, DateTime.Parse("1/1/2002"));
    aEmployees[3] = new Employee2(3, DateTime.Parse("1/1/2004"));

    // Now sort using the default sort (IComparable) which sorts on the id
    Array.Sort(aEmployees);
    foreach (Employee2 e in aEmployees)
        Trace.WriteLine(e.ID);

    // Sort using IComparere
    Array.Sort(aEmployees, Employee2.GetAscEmployeeDOB());
    foreach (Employee2 e in aEmployees)
        Trace.WriteLine(e.DOB);

    Array.Sort(aEmployees, Employee2.GetDescEmployeeDOB());
    foreach (Employee2 e in aEmployees)
        Trace.WriteLine(e.DOB);

    // We can also compare elements directly
    if (aEmployees[0] < aEmployees[1])
        Trace.WriteLine(aEmployees[0].ID + " is < " + aEmployees[1].ID);
}

IComparable<T> and IComparer<T>

IComparable<T> and IComparer<T> are the generic equivalents of IComparable<T> and IComparer<T>. In other words, these interfacs support ordering comparisons, i.e., their methods are used to indicate if two objects sort the same. IComparable<T> is often implemented by types whose values can be ordered. This interface finds its main use in Array.Sort<T>( T[] arr ) method which sorts the elements of the array arr using the IComparable<T> implementation of each element in the array. IComparer<T> on the other hand, is not implemented on a type directly, but rather it is implemented on helper objects, typically to provide multiple sort orders. Again, this interface finds its main use in System.Collections.Generic.List.Sort and System.Collections.Generic.List.BinarySearch methods.

class Employee : IComparable<Employee>
{
    // Data members
    private int nID;
    private DateTime dtDOB;

    // Constructors
    public Employee(int n, DateTime dt)
    {
        nID = n;
        dtDOB = dt;
    }

    // Properties
    public int      ID  { get { return nID; } }
    public DateTime DOB { get { return dtDOB; } }

    /* IComparable<Employee> Members */

    // IComparable<T> interface performs the default comparison since this object derives
    // directly from IComparable<T>. The comparison is performed on the ID field. Also note
    // how comparison is being performed using the IComparable<int>.CompareTo implementation
    // of nID field member

    public int CompareTo(Employee other)
    {
        // Because the CompareTo<T> method is strongly typed, it is not necessary to test
        // that the supplied object has the correct type. However, we still need to check
        // for comparison against self, or for comparison against null
        if (other == null) return -1;
        if (other == this) return 0;

        // Do actual comparison. Uses IComparable<int>.CompareTo implementation of nID
        return nID.CompareTo(other.nID);
    }

     /* IComparer implementations */

    // IComaprer class: This is a custom comparer that compares DOBs in an ascending order
    private class AscEmployeeDOB : System.Collections.Generic.IComparer<Employee>
    {
        #region IComparer<Employee> Members
        public int Compare(Employee eLHS, Employee eRHS)
        {
            return eLHS.DOB.CompareTo(eRHS.DOB);
        }
        #endregion
    }

    // IComaprer class: This is a custom comparer that compares DOBs in an descending order
    private class DescEmployeeDOB : System.Collections.Generic.IComparer<Employee>
    {
        #region IComparer<Employee> Members
        public int Compare(Employee eLHS, Employee eRHS)
        {
            return -1 * eLHS.DOB.CompareTo(eRHS.DOB);
        }
        #endregion
    }
   
    public static System.Collections.Generic.IComparer<Employee> GetAscEmployeeDOB()
    {
        return new Employee.AscEmployeeDOB();
    }

    public static System.Collections.Generic.IComparer<Employee> GetDescEmployeeDOB()
    {
        return new Employee.DescEmployeeDOB();
    }
}

private static void TestIComparableGeneric()
{
    // Create an array of employees
    Generic.Employee[] aEmployees = new Generic.Employee[4];
    aEmployees[0] = new Generic.Employee(4, DateTime.Parse("1/1/2001"));
    aEmployees[1] = new Generic.Employee(2, DateTime.Parse("1/1/2003"));
    aEmployees[2] = new Generic.Employee(1, DateTime.Parse("1/1/2002"));
    aEmployees[3] = new Generic.Employee(3, DateTime.Parse("1/1/2004"));

    // Now sort using the default sort (IComparable<Generic.Employee>) which sorts on the id
    Array.Sort(aEmployees);
    foreach (Generic.Employee e in aEmployees)
        Trace.WriteLine(e.ID);

    Array.Sort<Generic.Employee>(aEmployees, Generic.Employee.GetAscEmployeeDOB());
    foreach (Generic.Employee e in aEmployees)
        Trace.WriteLine(e.DOB);

    Array.Sort<Generic.Employee>(aEmployees, Generic.Employee.GetDescEmployeeDOB());
    foreach (Generic.Employee e in aEmployees)
        Trace.WriteLine(e.DOB);
}

Output:
1
2
3
4

01/01/2001 00:00:00
01/01/2002 00:00:00
01/01/2003 00:00:00
01/01/2004 00:00:00

01/01/2004 00:00:00
01/01/2003 00:00:00
01/01/2002 00:00:00
01/01/2001 00:00:00

Note the following distinction between IComparable<T> and IEquatable<T>: IComparable<T> defines CompareTo(T) method which is used to determine the sort order of instances implementing this interface. IEquatable<T> on the other hand, defines the Equals(T) method which determines the equality of instances of the implementing type.

Note: Consider List<T> collection, which is the generic equivalent of ArrayList. If type T implements the generic IComparable, then the default comparer is IComparable<T>.CompareTo method. Otherwise, if type T implements the non-generic IComparable, then the default comparer is IComparable.CompareTo method. If type T implements neither interface, then there is no default comparer and a comparer or comparison delegate must be provided explicitly.

IEquatable<T>

This is a new interface in .NET Framework version 2.0. It is very closely related to object.Equals method. IEquatable<T>, which exposes a single Equals(T obj) method, indicates whether the implementing object is equal to another object of the same type.

Note the following important points:

class Person : IEquatable<Person>
{
    // Member variables
    private string m_strName;
    private DateTime m_dtDOB;

    // Constructors
    public Person(string name, DateTime dob)
    {
        m_strName = name;
        m_dtDOB = dob;
    }

    // Properties
    public string Name  { get { return m_strName; } }
    public DateTime DOB { get { return m_dtDOB; } }

    #region IEquatable<Person> Members
    public bool Equals(Person obTarget)
    {
        // No need to check that obTarget is of the same type as it is strongly typed
        // and already of type Person. Hence type-safety. However, we need to check that
        // we are not checking against self or against null
        if (obTarget == this) return true;
        if (obTarget == null) return false;

        if ((obTarget.DOB == this.DOB) && (obTarget.Name == this.Name))
            return true;
        else
            return false;
    }
    #endregion
}

private static void TestIEquatable()
{
    Person p1 = new Person("X", DateTime.Parse("01/01/2000"));
    Person p2 = new Person("Y", DateTime.Parse("01/01/2001"));
    Person p3 = new Person("X", DateTime.Parse("01/01/2000"));

    // Check for equality
    bool b1 = p1.Equals(p2);         // false. invokes IEquatable<Person>.Equals
    bool b2 = p1.Equals(p3);         // true.  invokes IEquatable<Person>.Equals
    bool b3 = p1.Equals(p1);         // true.  invokes IEquatable<Person>.Equals
    bool b4 = p1.Equals(null);       // false. invokes IEquatable<Person>.Equals
    bool b5 = p1.Equals("junk");     // false. invokes object.Equals
}

IEqualityComparer & IEqualityComparer<T>

These interfaces allow collections to compare objects for equality. Although this functionality is very similar to Object.Equals, IComparable, IComparable<T>, and IEquatable<T>IEqualityComparer and its generic counterpart should be implemented by a class  that is used by a collection that accepts an IEqualityComparer (i.e., HashTable, OrderedDictionary, Dictionary, etc.). The following example illustrates:

class Person : IEqualityComparer<Person>, IEquatable<Person>
{
    // Member variables
   
private string m_strName;
    private DateTime m_dtDOB;

    // Constructors
   
public Person(string name, DateTime dob)
    {
        m_strName = name;
        m_dtDOB = dob;
    }
    public Person() : this(string.Empty, DateTime.MinValue) { }

    // Properties
   
public string Name { get { return m_strName; } }
    public DateTime DOB { get { return m_dtDOB; } }

    public static Generic.Person GetEqualityComparer() { return new Generic.Person(); }

    #region IEquatable<Person> Members
    public bool Equals(Person obTarget)
    {
        // No need to check that obTarget is of the same type as it is strongly typed
        // and already of type Person. Hence type-safety. However, we need to check that
        // we are not checking against self or againts null
        if (obTarget == this) return true;
        if (obTarget == null) return false;

        if ((obTarget.DOB == this.DOB) && (obTarget.Name == this.Name))
            return true;
        else
            return false;
    }
    #endregion

    #region IEqualityComparer<Person> Members
    bool IEqualityComparer<Person>.Equals(Person x, Person y)
    {
        // Delegate to IEquatable<T> implementation
        return x.Equals(y);
    }

    int IEqualityComparer<Person>.GetHashCode(Person obj)
    {
        return Name.GetHashCode() ^ DOB.GetHashCode();
    }
    #endregion
}

private static void TestIEqualityComparer()
{
    // Add items to the dictionary. First and last items are the same
   
Dictionary<Generic.Person, int> obDict = new Dictionary<Generic.Person, int>( Generic.Person.GetEqualityComparer() );
    obDict.Add( new Generic.Person("X", DateTime.Parse( "01/01/2000") ), 1);
    obDict.Add( new Generic.Person("Y", DateTime.Parse( "02/02/2001") ), 2);
    obDict.Add( new Generic.Person("Z", DateTime.Parse( "03/03/2003") ), 3);

    // Now iterate over contents of obDict dictionary
   
string strPerson = String.Empty;
    foreach (KeyValuePair<Generic.Person, int> kvp in obDict)
    {
        Generic.Person p = (Generic.Person)kvp.Key;
        strPerson = p.Name + " " + p.DOB;
        Trace.WriteLine(string.Format("(key, value) = ({0}, {1})", strPerson, kvp.Value));
    }
}

The non-generic IEqualityComparer is implemented just like any other interface, however, its generic counterpart is typically implemented using a class derived from EqualityComparer. The Default property of EqualityComparer class checks whether type T implements IEquatable<T>, and if so, returns an EqualityComparer that uses that implementation. Otherwise, Default returns an EqualityComparer that  uses the overrides of Object.Equals and Object.GetHashCode.

IObserver<T> & IObervable<T>

The IObserver<T> and IObservable<T> interfaces provide a generalized mechanism for push-based notification, also known as the observer design pattern, or publisher-subscriber:

At any given time, a given provider (class that implements IObservable<T>) may have zero, one, or multiple observers (IObserver<T> instances) . The provider is responsible for storing references to observers and ensuring that they are valid before it sends notifications. The provider must implement a single method, Subscribe, which is called by each observer that wants to receive push-based notifications. Note that a particular instance of an IObservable<T> implementation is responsible for handling all subscriptions and notifying all subscribers. Observers should make no assumptions about the IObservable<T> implementation, such as the order of notifications that multiple observers will receive. The provider sends the following three kinds of notifications to the observer by calling IObserver<T> methods:

Note that IObservable<T>.Subscribe method must be called to register an observer for push-based notifications. A typical implementation of the Subscribe method does the following:

Example
namespace Framework4.IObservable
{
    /* This class represents T in the discussion above. In other words, it is the data that the       observers will receive from publisher */     struct Location     {            public double Latitude {getprivate set;}
        public double Longitude { getprivate set; }

        public Location(double lat, double lng) : this()
        {
            Latitude = lat;
            Longitude = lng;
        }
    }
}
namespace Framework4.IObservable
{
    /*  This is the publisher. It publishes Location data to all subsribers */     class LocationPublisher : IObservable<Location>
    {
        private List<IObserver<Location>> observers;

        public LocationPublisher()
        {
            observers = new List<IObserver<Location>>();
        }

        // IObservable<T> implementation: register new subscribers         public IDisposable Subscribe(IObserver<Location> observer)
        {
            if (!observers.Contains(observer))
                observers.Add(observer);
            return new Unsubscriber(observers, observer);
        }

        // Publish location data to all registerd observers.         public void TrackLocation(Nullable<Location> loc)
        {
            foreach (var observer in observers)
            {
                if (!loc.HasValue)
                    observer.OnError(new ApplicationException("Unknown location"));
                else                     observer.OnNext(loc.Value);             }         }         // Indicate to observers that publisher has finished sending data. Method calls each         // observer's OnCompleted method and then clears the internal list of observers.         public void EndTransmission()
        {
            foreach (var observer in observers.ToArray())
                if (observers.Contains(observer))
                    observer.OnCompleted();

            observers.Clear();
        }

        // An instance of this class is returned to subscribers to allow them to unsubscribe         private class Unsubscriber : IDisposable
        {
            private List<IObserver<Location>> _observers;
            private IObserver<Location> _observer;

            public Unsubscriber(List<IObserver<Location>> observers, IObserver<Location> observer)
            {
                _observers = observers;
                _observer = observer;
            }

            public void Dispose()
            {
                if (_observer != null && _observers.Contains(_observer))
                    _observers.Remove(_observer);
            }
        }
    }
}
namespace Framework4.IObservable
{
    /*  This is the subscriber. It subscribes to Location data from the publisher. It also includes         a Subscribe method, which wraps a call to the provider's Subscribe method. This allows the         method to assign the returned IDisposable reference to a private variable. The LocationReporter         class also includes an Unsubscribe method, which calls the IDisposable.Dispose method of the         object that is returned by the IObservable<T><.Subscribe method      */     class LocationSubscriber : IObserver<Location>
    {
        private IDisposable unsubscriber;
        public string Name { getprivate set; }

        public LocationSubscriber(string name)
        {
            Name = name;
        }
        
        // Gets an instance of the publishser and subscribes to its Location data         public virtual void Subscribe(IObservable<Location> provider)
        {
            if (provider != null)
                unsubscriber = provider.Subscribe(this);
        }

        // Called by publisher when data publishing is completed         public virtual void OnCompleted()
        {
            Console.WriteLine("The Location Tracker has completed transmitting data to {0}.",  Name);
            this.Unsubscribe();
        }

        // Called by publisher when data publishing encounters an error         public virtual void OnError(Exception e)
        {
            Console.WriteLine("{0}: The location cannot be determined.", Name);
        }

        // Called by publisher when publishing new data         public virtual void OnNext(Location value)
        {
            Console.WriteLine("{2}: The current location is {0}, {1}"
value.Latitude, value.Longitude, Name);
        }

        // Called by the main application to unsubscribe         public virtual void Unsubscribe()
        {
            unsubscriber.Dispose();
        }
    }
}
namespace Framework4.IObservable
{
    class ObservableTest     {         public void Test()
        {
            // Define a provider and two observers.             LocationPublisher provider = new LocationPublisher();
            LocationSubscriber sub1 = new LocationSubscriber("Subscriber 1");
            sub1.Subscribe(provider);
            LocationSubscriber sub2 = new LocationSubscriber("Subscriber 1");
            sub2.Subscribe(provider);

            // Publisher typically runs in a separate thread that receives location information             // from some external data source and then publishes them on to all registered             // observers. In the example, publisher is invoked explicitly to publish data             provider.TrackLocation(new Location(47.6456, -122.1312));

            // Once sub1 is unsubscribed, only sub2 will receive notifations             sub1.Unsubscribe();             // Publish more data. Only sub2 will receive notifations             provider.TrackLocation(new Location(47.6677, -122.1199));

            // Publish an erroneous location             provider.TrackLocation(null);

            // End transmissio             provider.EndTransmission();         }     } }

Program output is shown below:

ISet<T>

ISet<T> interface provides methods for implementing sets, which are collections that have unique elements and specific operations. ISet<T> is implemented by HashSet<T> and SortedSet<T>. Recall that a set is a collection of unique and unordered elements. This means that a set does not allows duplicates and set elements cannot be accessed by index. HashSet<T> and SortedSet<T> both have the following important features:

HashSet<T> and SortedSet<T> both implement ISet<T>. HashSet<T> is implemented using a hashtable that stores just keys, while SortedSet<T> is implemented with a red/black tree:

public class HashSet<T> : ISerializableIDeserializationCallbackISet<T>,
ICollection<T>, IEnumerable<T>, IEnumerable { ... }
public class SortedSet<T> : ISet<T>, ICollection<T>, IEnumerable<T>, ICollectionIEnumerable,
ISerializableIDeserializationCallback { ... }

SortedSet<T> offers the same set of members as HashSet<T>, plus the following:

  1. GetViewBetween

  2. Reverse

  3. Min

  4. Max

 Interfaces in .NET Collections

Decoumentation, or even decompiling  List<T> class, shows the following:

public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>, IEnumerableIList
ICollectionIReadOnlyList<T>, IReadOnlyCollection<T>

Note the following: