背景:我们知道在一个泛型实体集中要想实现按实体属性排序是比较容易的,一般就是调用 List<T>.Sort方法,或者是在插入数据时就进行排序,也就是先调用List.BinarySearch 方法,得到插入的索引位置。如果在一个实体集中要实现多个字段之间相互变换的排序(有时按属性1,有时按属性2,并非同时按多个属性排序),我们是不是可 以对这个操作进行些额外的封装呢?这里的排序我用List.BinarySearch,大家当然也可以优胜List.Sort方法。
首先:我们来看下面这个实体类,共包含两个字段,一个数字类型,一个日期类型,我想要实现字
段排序间的动态变换,也就是说有时按数字排,有时按日期排序。
/// 实体类
/// </summary>
public class TrainingBrief
{
public TrainingBrief()
{ }
#region Model
private int _id;
private DateTime _trainingdate;
/// <summary>
/// 自增长
/// </summary>
public int ID
{
set { _id = value; }
get { return _id; }
}
/// <summary>
/// 日期
/// </summary>
public DateTime TrainingDate
{
set { _trainingdate = value; }
get { return _trainingdate; }
}
#endregion Model
}
第一:不做封装的实现方式:写两个
Comparer:IntComparer,DateTimeComparer,代码如下:
/// 按实体类的日期排序
/// </summary>
public class DateTimeComparer : IComparer<TrainingBrief>
{
public int Compare(TrainingBrief _x, TrainingBrief _y)
{
DateTime x = (DateTime)_x.TrainingDate;
DateTime y = (DateTime)_y.TrainingDate;
//比较结果
int result = 0;
if (x == null)
{
if (y == null)
{
// If x is null and y is null, they're
// equal.
result = 0;
}
else
{
// If x is null and y is not null, y
// is greater.
result = -1;
}
}
else
{
// If x is not null
//
if (y == null)
// and y is null, x is greater.
{
result = 1;
}
else
{
result = x.CompareTo(y);
}
}
//这种结果为升序
//转换成降序
switch (result)
{
case 1:
result = -1;
break;
case -1:
result = 1;
break;
}
return result;
}
}
/// <summary>
/// 按实体类的数字排序
/// </summary>
public class IntComparer : IComparer<TrainingBrief>
{
public int Compare(TrainingBrief _x, TrainingBrief _y)
{
//方法和上面的类似
}
}
其次是调用方式:对于日期排序
for (int i = 0; i < 3; i++)
{
TrainingBrief model = new TrainingBrief();
Random ra = new Random();
model.ID = ra.Next(100000, 999999);
model.TrainingDate = DateTime.Now;
//实例化Comparer
DateTimeComparer dc = new DateTimeComparer();
this.SearchAndInsert(_list, model, dc);
Thread.Sleep(1000);
}
/// <summary>
/// 使用指定的比较器在整个已排序的 List 中搜索元素,并返回该 元素从零开始的索引。
/// 然后利用返回的索引把记录插入到列表中
/// </summary>
/// <param name="list">数据列表</param>
/// <param name="insert">要插入的内容</param>
/// <param name="dc">排序类</param>
private void SearchAndInsert(List<TrainingBrief> list,
TrainingBrief insert, DateTimeComparer dc)
{
int index = list.BinarySearch(insert, dc);
if (index < 0)
{
list.Insert(~index, insert);
}
}
如果要想实现数字排序,我们还需要另外写一个SearchAndInsert方法,因为接受的ICompaer类型不同。这样总觉的不太 OO。
第二:封装后的做法。下面是我的改造过程:
1:构造一个泛型接口,它实现IComparer<T>接口。里面没有定义任何方法,因为
IComparer<T>中已经包含了public int Compare(T,x,T y)方法。
/// 排序的泛型接口
/// </summary>
/// <typeparam name="T">实体类</typeparam>
public interface ICustomComparer<T>:IComparer<T>
{
}
2:日期排序的Comparer
/// 按实体类的日期排序
/// </summary>
public class DateTimeComparer : ICustomComparer<TrainingBrief>
{}
3:数字排序的Comparer
/// 按实体类的数字排序
/// </summary>
public class IntComparer : ICustomComparer<TrainingBrief>
{}
4:客户端调用:唯一的变化就是创建Comparer:
修改SearchAndInsert方法的参数,让它支持多个Comparer,这样就不会根据不同的排序方式创建一个SearchAndInsert方
法了。
TrainingBrief insert, ICustomComparer<TrainingBrief> dc)
{}
如果觉的上面的还不够OO,我们可以把SearchAndInsert方法单独提出来,放入一个泛型类中。
{
private void SearchAndInsert(List<T> list,
T insert, ICustomComparer<T> dc)
{}
}
优点:1)所有的实例集的属性排序都可以利用了这个泛型接口;
2) 排序字段发生变化时非常容易控制,我们可以结合工厂模式,来动态创建具体的Comparer。
篇外话:我们项目开发时,有时总感觉一些什么算法啊什么的,不知道怎么用,这篇文章,里面就 应用了很多这方面的知识,像List<T>.Sort实现时就是采用的快速排序,调用List.BinarySearch 时,应用了~操作符,而它正是学校学的取反操作。