Table of Contents
- Introduction
- Prerequisites
- Case Study: Problem Statement
- Create Generic Interface IBaseComparer
- Create Class Employee and Implement IBaseComparer
- Create Generic BaseCompare
- Utility Class
- Entry Point: Program
- Reverse Engineering: Sequence Diagram
- Conclusion
- Reference
The purpose of this article is to showcase the power of Generics that we have in C#. Initially, I was working on IComparable
and IComparer
for a custom sorting operation; there were scenarios when I wanted to generalize certain parts of my code and I failed to achieve the same. The only reason I was not able to do that was due to lack of knowledge of Generics. I explored the basic concepts of C# Generics that encompasses constraints, covariance and contravariance, and nullable types. I did lots of reading online during my leisure time, and finally I was able to crack the solution.
While doing a sort operation, we use the Interface IComparer
and implement this IComparer
in nested class results in a lot of duplicate code. The aim was to replace the duplications with a more generalized construct for all other domain classes that I had in my application class library.
public class Country
{
private int m_CountryID = int.MinValue;
private string m_CountryName = string.Empty;
private int? m_CountryTradeCode = null;
public Country()
{
}
public Country(int countryID)
{
m_CountryID = countryID;
}
public Country(int countryID, string countryName, int? countryTradeCode)
: this(countryID)
{
m_CountryName = countryName;
m_CountryTradeCode = countryTradeCode;
}
public int CountryID
{
get { return m_CountryID; }
set { m_CountryID = value; }
}
public int? CountryTradeCode
{
get { return m_CountryTradeCode; }
set { m_CountryTradeCode = value; }
}
public string CountryName
{
get { return m_CountryName; }
set { m_CountryName = value; }
}
public int CompareTo(Country rhs,
Country.CountryComparer.CountryComparisonType which)
{
switch (which)
{
case CountryComparer.CountryComparisonType.CountryID:
return this.m_CountryID.CompareTo(rhs.m_CountryID);
case CountryComparer.CountryComparisonType.CountryName:
return this.m_CountryName.CompareTo(rhs.m_CountryName);
case CountryComparer.CountryComparisonType.CountryTradeCode:
return this.m_CountryTradeCode.GetValueOrDefault().CompareTo(
rhs.m_CountryTradeCode.GetValueOrDefault());
}
return 0;
}
public class CountryComparer : IComparer< Country >
{
public enum CountryComparisonType
{
CountryID,
CountryName,
CountryTradeCode,
NULL
}
private CountryComparisonType _whichComparison;
private Utility.SortOrder _sortDirection;
public CountryComparisonType WhichComparison
{
get { return _whichComparison; }
set { _whichComparison = value; }
}
public int Compare(Country lhs, Country rhs)
{
if (SortDirection == Utility.SortOrder.Asc)
return lhs.CompareTo(rhs, WhichComparison);
else
return rhs.CompareTo(lhs, WhichComparison);
}
public bool Equals(Country lhs, Country rhs)
{
return this.Compare(lhs, rhs) == 0;
}
public int GetHashCode(Country e)
{
return e.GetHashCode();
}
public Utility.SortOrder SortDirection
{
get { return _sortDirection; }
set { _sortDirection = value; }
}
}
}
In the above code snippet, the nested class CountryComparer
is very much tied to the country class, and it implements IComparer< Country >
. My idea was to decouple this CountryComparer
class into a more reusable piece of code.
In the above code, if I've to create another domain class, then I need to implement IComparer
. In the process, I ended up creating a nested class for each such domain class. For example:
public class Employee
{
public int CompareTo(Employee obj,
EmployeeComparisonType sortexpression)
{
}
class EmployeeComparer: IComparer < Employee>
public int IComparer.Compare()
{
return rhs.CompareTo(lhs,WhichComparison);
}
}
My objective is to decouple the nested class CountryComparer
into the BaseComparer
class such that it is independent of any class entity. The challenge I faced was to create a generic method CompareTo()
in Country
which can take any class object and sort expression. The only problem here is that the sorting occurs within objects of its own class and the CompareTo()
method is in the main class construct called Country
.
In order to segregate the Compare
method from the generic object and sort expression, we need to create a generalised Compare()
method in the BaseComparer
class. For this to happen, we need to create a contract method CompareTo
in the Interface IBaseComparer
.
T: Object{Employee,Country,Location....Domain Class Intance}
N: Enum {WhichComparer-Sort On}
Solution: Create an Interface and reference it in BaseComparer
using a Constraint.
namespace Domain
{
public interface IBaseComparer < T,N >
{
int CompareTo(T obj,N whichComparer);
}
}
With the help of the interface, we made CompareTo()
available to all classes so as to implement it. Now, compareTo()
is generic to all class entities. In the below case, all subscriber classes must declare its respective object type and comparison type.
IBaseComparer < T,N > :IBaseComparer <Employee, Domain.Utility.EmployeeComparisonType>
IBaseComparer < T,N > :IBaseComparer <Country, Domain.Utility.CountryComparisonType>
namespace Domain
{
public class Employee : IBaseComparer <Employee, Domain.Utility.EmployeeComparisonType>
{
private int m_EmployeeID = int.MinValue;
private string m_EmployeeName = string.Empty;
private int? m_EmployeeTradeCode = null;
public Employee(){}
public Employee(int employeeID)
{
m_EmployeeID = employeeID;
}
public Employee(int employeeID, string employeeName,
int? employeeTradeCode): this(employeeID)
{
m_EmployeeName = employeeName;
m_EmployeeTradeCode = employeeTradeCode;
}
public int employeeID
{
get { return m_EmployeeID; }
set { m_EmployeeID = value; }
}
public int? EmployeeTradeCode
{
get { return m_EmployeeTradeCode; }
set { m_EmployeeTradeCode = value; }
}
public string EmployeeName
{
get { return m_EmployeeName; }
set { m_EmployeeName = value; }
}
public int CompareTo(Employee rhs,Utility.EmployeeComparisonType WhichComparer)
{
switch (WhichComparer)
{
case Utility.EmployeeComparisonType.EmployeeID:
return this.m_EmployeeID.CompareTo(rhs.m_EmployeeID);
case Utility.EmployeeComparisonType.EmployeeName:
return this.m_EmployeeName.CompareTo(rhs.m_EmployeeName);
case Utility.EmployeeComparisonType.EmployeeTradeCode:
return this.m_EmployeeTradeCode.GetValueOrDefault().CompareTo(
rhs.m_EmployeeTradeCode.GetValueOrDefault());
}
return 0;
}
}
}
Now the challenge is to get this tied to the Comparer
nested class which we planned to separate out. For this, we create the BaseComparer
class which takes a generic parameter of Class T
object and the N WhichComparer
field.
Now, to invoke CompareTo
of the respective class, like Employee
, Customer
, Country
etc., we need to assign BaseCompare
with the constraint, as where T : IBaseComparer<T,N>
. Using the constraint, we instruct the compiler to use the specification of the Contract methods and properties of the interface. The interesting point here is that we have not implemented the Interface here. We have not defined the method CompareTo
in the code below. The constraint helped us to invoke the contract method from the class that has implemented it.
The best part is using the constraint to setup a relationship with two different classes. If you look thoroughly, the Employee
class and the BaseCompare
class have a relationship due to the Interface IBaseComparer
. Unlike the problem statement code, we see that the class Country
is tightly coupled with the nested class CountryComparer
.
To my rescue, I got constraints in Generics, which helped me to surpass all these hurdles. Trust me, it took a week to get to the workaround. The only reason behind is not having touched base on Generics for a long time.
namespace Domain
{
public class BaseCompare <T,N> : IComparer<T> where T :
IBaseComparer<T,N>
{
private Utility.SortOrder _sortDirection;
private N _whichComparison;
public N WhichComparer
{
get
{
return _whichComparison;
}
set
{
_whichComparison = value;
}
}
public int Compare(T lhs, T rhs)
{
if (SortDirection == Utility.SortOrder.Asc)
return lhs.CompareTo(rhs, WhichComparer);
else
return rhs.CompareTo(lhs,WhichComparer);
}
public bool Equals(T lhs, T rhs)
{
return this.Compare(lhs, rhs) == 0;
}
public int GetHashCode(object e)
{
return e.GetHashCode();
}
public Utility.SortOrder SortDirection
{
get { return _sortDirection; }
set { _sortDirection = value; }
}
}
The utility class will have all sort expression definitions and the sort order.
namespace Domain
{
public class Utility
{
public enum EmployeeComparisonType
{
EmployeeID,
EmployeeName,
EmployeeTradeCode,
NULL
}
public enum SortOrder
{
Asc,
Desc
}
}
}
In the code snippet below, we consumed the sorting logic for the IList
collection of Employee
objects.
namespace GenricsConsoleApp
{
class Program
{
private Utility.SortOrder m_SortOrder;
private Utility.EmployeeComparisonType m_SortColumn;
private IList <Employee> m_EmployeeDataSource;
public Utility.EmployeeComparisonType SortColumn
{
get { return m_SortColumn; }
set { m_SortColumn = value; }
}
public Utility.SortOrder SortDirection
{
get { return m_SortOrder; }
set { m_SortOrder = value; }
}
public IList <Employee> EmployeeDataSource
{
get { return m_EmployeeDataSource; }
set { m_EmployeeDataSource = value; }
}
static void Main(string[] args)
{
Program obj = new Program();
obj.SortDirection = Utility.SortOrder.Desc;
obj.SortColumn = Utility.EmployeeComparisonType.EmployeeName;
obj.SortEmployeeList(Utility.EmployeeComparisonType.EmployeeName);
foreach (Employee emp in obj.EmployeeDataSource)
{
Console.WriteLine(emp.EmployeeName);
}
}
public void LoadEmployeeList()
{
SortEmployeeList(Utility.EmployeeComparisonType.NULL);
}
private void SortEmployeeList(Utility.EmployeeComparisonType sortExpression)
{
List <Employee> employeeList = (List <Employee>)GetEmployeeList();
BaseCompare <Employee, Utility.EmployeeComparisonType>
employeeComparer = new BaseCompare <Employee,
Utility.EmployeeComparisonType>();
if (Utility.EmployeeComparisonType.NULL != sortExpression)
{
if (sortExpression == SortColumn)
{
if (SortDirection == Utility.SortOrder.Asc)
{
SortDirection = Utility.SortOrder.Desc;
}
else
{
SortDirection = Utility.SortOrder.Asc;
}
}
else
{
SortDirection = Utility.SortOrder.Asc;
}
SortColumn = sortExpression;
employeeComparer.WhichComparer = SortColumn;
employeeComparer.SortDirection = SortDirection;
employeeList.Sort(employeeComparer);
m_EmployeeDataSource = employeeList;
}
private IList <Employee> GetEmployeeList()
{
IList <Employee> employeeList = new List <Employee>();
employeeList.Add(new Employee(1, "Santosh Poojari", 100001));
employeeList.Add(new Employee(2, "Sandhya", null));
employeeList.Add(new Employee(3, "Darsh", null));
employeeList.Add(new Employee(4, "Gautam Sharma", 100004));
employeeList.Add(new Employee(5, "Shawn Miranda", 100005));
employeeList.Add(new Employee(6, "Karan", 100006));
employeeList.Add(new Employee(7, "Rajan Golambade", 100007));
employeeList.Add(new Employee(8, "Swati Lakshminarayan", 100008));
employeeList.Add(new Employee(9, "Vaibhav", null));
employeeList.Add(new Employee(10, "Rahul Mahurkar", 100011));
employeeList.Add(new Employee(11, "Abdul Qabiz", 100009));
employeeList.Add(new Employee(12, "Bibeka Pahi", null));
return employeeList;
}
}
}
Here is the complete flow of sorting for a given collection objects for a better understanding. You can get this sequence diagram using the VS2010 Ultimate edition. In VS2010, there are options to create this diagram under the 'Architecture' tab in the main menu.
Any ideas, corrections, and suggestions are most welcome.