| Interface | When to use |
| Allows a class to perform deep-copying of its instances. | |
| 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> interface provides methods for implementing sets, which are collections that have unique elements and specific operations. |
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
}
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 );
}
}
The interface has been fully described in Fundamentals - System.Object under the Finalize section.
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 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.
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:
IComparable and IEquatable<T>: IComparable defines the CompareTo method which is typically used to determine the sort order of instances of the implementing type. IEquatable<T> on the other hand, defines the Equals method which determine the equality of instances of the implementing type.
IEquatable.Equals and
object.Equals: Both of these
Equals methods accomplish the same thing, but there are some
differences. To illustrate, consider List<T>
collection, which is the generic equivalent of ArrayList.
If type T implements
IEquatable, then the equality comparer is
IEquatable<T>.Equals method, otherwise the equality comparer is
object.Equals.
If type T in List<T> was a reference type,
there would be no difference in the behaviour of IEquatable.Equals and
object.Equals.
The difference between IEquatable.Equals and
object.Equals is more obvious when dealing with
value types as you have to deal with implementation and boxing issues. If
T was a value type and it did not implement
IEquatable, methods such as
Contains must call the object.Equals
method, which boxes the affected list element (a value type). This
obviously has performance and memory overhead.
Note: all value types in .NET 2.0 implement the
IEquatable<T> interface. For example,
System.Double inherits from IEquatable<double>,
among other interfaces.
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.
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:
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 {get; private set;}
public double Longitude { get; private 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 { get; private 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> 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:
Their Contains method executes very quickly using a hash-based lookup.
They do not store duplicate elements and silently ignore requests to add duplicates.
Elements cannot be accessed by position.
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> : ISerializable, IDeserializationCallback, ISet<T>,
ICollection<T>, IEnumerable<T>, IEnumerable { ... }
public class SortedSet<T> : ISet<T>, ICollection<T>, IEnumerable<T>, ICollection, IEnumerable,
ISerializable, IDeserializationCallback { ... }
SortedSet<T> offers the same set of members as HashSet<T>, plus the following:
GetViewBetween
Reverse
Min
Max
Decoumentation, or even decompiling List<T> class, shows the following:
public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>, IEnumerable, IList,
ICollection, IReadOnlyList<T>, IReadOnlyCollection<T>
Note the following:
interface I1 { void foo();}
interface I2 : I1 { void foo2();}
interface I3 : I2 { void foo3();}
// It is optional for class A1 to list I2 and I1 in its base type list
class A1 : I3
{ /* Should implement I3, I2 and I1 methods */ }
// A1 could have been declared as below, where the entire base types are listed
// This is optional
class A2 : I3, I2, I1
{ /* Should implement I3, I2 and I1 methods */ }
// Generic type parameter T must implement I3, I2 and I1, but it is optional for
// the constraint on T to state that
class B1<T> where T : I3 { }
// B1 could have been declared as below, where the entire base types are listed
// in T's constraint. This is optional
class B2<T> where T:, I3, I2, I1 {}
// It is optional to restate base interfaces on partial parts
partial class C : I3 {}
partial class C {}
public class List<T> : IList<T>, IListDocumentation for List<T> and other similar classes (in terms of interface derivation) usually list the full interface list because it is documentation; it gives you all the information you need in one place rather than having to jump through many different documentation pages to find the complete list. And even though tools like ILSpy and object browses show the whole base type list, these tools do not have the source code and they display this information (the full base type list) from meta data. Again these tools work like documentation, they are showing the full information in one place rather than having to jump through different pages.
interface I1 { void foo();}Every interface method of every interface you implement in a class or struct has to be mapped to a method in the implementing type. The mapped-to method in the implementing type is either a method implemented directly by the type or a method that the type obtained via inheritance. In the example above. class D automatically implements interfaces I1, I2 and I3 via class A. If you restate on a derived class that the derived class implements an interface already mapped by the base class, then the interface method implementation is mapped to the derived type. In the example above, If you restate the interfaces in D's base class list, then the C# compiler will do an interface reimplementation. This technique is useful if the base class does an explicit interface implementation and you would like to replace this implementation with you own:
interface I2 : I1 { void foo2();}
interface I3 : I2 { void foo3();}
// It is optional for class A1 to list I2 and I1 in its base type list
class A1 : I3
{ /* Should implement I3, I2 and I1 methods */ }
public class D : A1 {}
public interface I1
{
void foo();
}
public class A : I1
{
public void foo() { Trace.WriteLine("A.foo"); }
}
public class B : A
{
}
public class C : A, I1
{
public void foo() { Trace.WriteLine("C.foo"); }
}
public class AV : I1
{
public virtual void foo() { Trace.WriteLine("AV.foo"); }
}
public class BV : AV
{
}
public class CV : AV // Adding I1 to the base type list is optional
{
public override void foo() { Trace.WriteLine("CV.foo"); }
}
public class AE : I1
{
void I1.foo() { Trace.WriteLine("AE.foo"); }
}
public class BE : AE
{
}
public class CE :AE, I1
{
public void foo() { Trace.WriteLine("CE.foo"); }
}
public static class MyImmutables
{
static public void TestInterfaces1()
{
A a = new A();
a.foo(); // A.foo
(a as I1).foo(); // A.foo
A b = new B();
b.foo(); // A.foo
(b as I1).foo(); // A.foo
A c = new C();
c.foo(); // A.foo
(c as I1).foo(); // C.foo
}
static public void TestInterfaces2()
{
AV av = new AV();
av.foo(); // AV.foo
(av as I1).foo(); // AV.foo
AV bv = new BV();
bv.foo(); // AV.foo
(bv as I1).foo(); // AV.foo
AV cv = new CV();
cv.foo(); // CV.foo
(cv as I1).foo(); // CV.foo
}
static public void TestInterfaces3()
{
AE av = new AE();
(av as I1).foo(); // AE.foo
AE be = new BE();
(be as I1).foo(); // AE.foo
AE ce = new CE();
(ce as I1).foo(); // CE.foo
}
}
public interface IEnumerable<out T> : IEnumerable { }This is because IEnumerable<T> can be made covariant in T (out T) but IList<T> cannot. A sequence of integers can be treated as a sequence of objects, by boxing every integer as it comes out of the sequence. In a read-write collections such as List<int>, integers cannot be treated as read-write list of objects because you can put a string into the list. IEnumerable<T> can easily fulfill the contact of IEnumerable just be adding a boxing helper method. But IList<T> is not required to fulfill the whole contract of IList, so IList<T> does not inherit from IList.
public interface ICollection<T> : IEnumerable<T> { }
public interface IList<T> : ICollection<T> { }