C# 泛型的强大之处 - 约束






3.80/5 (9投票s)
我们的目标是充分利用泛型来提高代码的可重用性和性能。
目录
- 引言
- 必备组件
- 案例研究:问题陈述
- 创建泛型接口 IBaseComparer
- 创建类 Employee 并实现 IBaseComparer
- 创建泛型类 BaseCompare
- 工具类
- 入口点:Program
- 逆向工程:序列图
- 结论
- 参考
引言
本文旨在展示 C# 中泛型的强大功能。起初,我正在研究 IComparable
和 IComparer
以实现自定义排序操作;当时我希望将代码的某些部分通用化,但未能实现。之所以不能实现,仅仅是因为缺乏对泛型的了解。我研究了 C# 泛型的基本概念,包括约束、协变和逆变以及可空类型。我利用业余时间阅读了大量在线资料,最终找到了解决方案。
必备组件
- VS 2010
- .NET 4.0
案例研究:问题陈述
在执行排序操作时,我们使用 IComparer
接口,并在嵌套类中实现此 IComparer
会导致大量重复代码。我们的目标是用更通用的结构来替换应用程序类库中其他所有域类中存在的重复代码。
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; }
}
}
}
在上面的代码片段中,嵌套类 CountryComparer
与 Country 类紧密耦合,并实现了 IComparer< Country >
。我的想法是将这个 CountryComparer
类解耦成一个更易于重用的代码片段。
在上面的代码中,如果我需要创建另一个域类,我就需要实现 IComparer
。在这个过程中,我为每个这样的域类创建了一个嵌套类。例如:
public class Employee
{
public int CompareTo(Employee obj,
EmployeeComparisonType sortexpression)
{
}
class EmployeeComparer: IComparer < Employee>
//Step 1 Create WhichComparison property
//Step 2 Implement IComparer.Compare()
public int IComparer.Compare()
{
//Invoke Employee class method Compare()
return rhs.CompareTo(lhs,WhichComparison);
}
}
我的目标是将嵌套类 CountryComparer
解耦到 BaseComparer
类中,使其独立于任何类实体。我面临的挑战是在 Country
类中创建一个通用的 CompareTo()
方法,该方法可以接受任何类对象和排序表达式。唯一的问题是排序发生在其自身类的对象之间,而 CompareTo()
方法位于名为 Country
的主类结构中。
创建泛型接口 IBaseComparer
为了将 Compare
方法与泛型对象和排序表达式分开,我们需要在 BaseComparer
类中创建一个通用的 Compare()
方法。为此,我们需要在 IBaseComparer
接口中创建一个契约方法 CompareTo
。
T:对象 {Employee, Country, Location.... 域类实例}
N:枚举 {WhichComparer-Sort On}
解决方案:创建一个接口,并通过约束在 BaseComparer
中引用它。
namespace Domain
{
public interface IBaseComparer < T,N >
{
int CompareTo(T obj,N whichComparer);
}
}
创建类 Employee 并实现 IBaseComparer
借助该接口,我们使 CompareTo()
可供所有类实现。现在,compareTo()
对所有类实体都是通用的。在下面的示例中,所有订阅类都必须声明其各自的对象类型和比较类型。
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;
}
}
}
创建泛型类 BaseCompare
现在,挑战是如何将其与我们计划分离的 Comparer
嵌套类关联起来。为此,我们创建了 BaseComparer
类,它接受一个泛型参数 Class T
对象和一个 N WhichComparer
字段。
现在,要调用相应类的 CompareTo
,例如 Employee
、Customer
、Country
等,我们需要将 BaseCompare
与约束一起指定,形式为 where T : IBaseComparer<T,N>
。使用此约束,我们指示编译器使用接口的契约方法和属性的规范。有趣之处在于,我们并未在此处实现接口。我们在下面的代码中没有定义 CompareTo
方法。约束帮助我们调用已实现它的类中的契约方法。
最好的部分是利用约束来建立两个不同类之间的关系。如果您仔细观察,Employee
类和 BaseCompare
类由于 IBaseComparer
接口而产生了关联。与问题陈述中的代码不同,我们看到 Country
类与嵌套类 CountryComparer
紧密耦合。
幸运的是,我发现了泛型中的约束,它帮助我克服了所有这些障碍。说实话,我花了一周时间才找到这个解决方法。唯一的原因是我很久没有接触过泛型了。
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; }
}
}
工具类
工具类将包含所有排序表达式的定义和排序顺序。
namespace Domain
{
public class Utility
{
public enum EmployeeComparisonType
{
EmployeeID,
EmployeeName,
EmployeeTradeCode,
NULL
}
public enum SortOrder
{
Asc,
Desc
}
}
}
入口点:Program
在下面的代码片段中,我们对 Employee
对象列表 IList
集合使用了排序逻辑。
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;
}
}
}
逆向工程:序列图
为了更好地理解,这是给定集合对象的完整排序流程。您可以使用 VS2010 Ultimate 版本获取此序列图。在 VS2010 中,可以在主菜单的“架构”选项卡下创建此图。
结论
欢迎提出任何想法、更正和建议。