C#.NET:重写 IComparable<T> 和 IComparer<T> 接口的基本功能,使用 Enumerable.OrderBy<TSource,TKey> 聚合方法对自定义数据集合进行排序。






4.99/5 (49投票s)
本文介绍如何通过重写泛型 IComparable<T> 和 IComparer<T> 接口的功能,使用 LINQ 的 Enumerable.OrderBy<TSource, TKey> 子句聚合方法对自定义非泛型集合进行排序。
引言
在 C#.NET 编码时,我们经常实现自定义集合来存储多个相同自定义数据类型的相关数据对象项,以及提供允许根据特定数据类型操作这些数据对象项的功能。在 C# 编程中,我们还使用 Microsoft .NET Framework 的泛型功能和其他语言扩展,如 LINQ,它们允许使用 LINQ 查询和聚合方法来操作存储在泛型数据集合中的数据项。不幸的是,这些功能通常只与泛型数据集合一起使用,如 List<T>、ArrayList、Stack<T> 等。本文介绍了一种方法,该方法可以在使用 LINQ 查询和聚合方法操作用户定义的自定义数据类型存储在自定义非泛型数据集合中的项时,为其提供扩展功能。
背景
在本文中,我们将讨论一个常见场景,该场景允许重写 LINQ 聚合方法 Enumerable.OrderBy<TSource, TKey>
的泛型功能,以对自定义非泛型数据集合中存储的特定用户定义数据类型的数据对象项执行升序排序。作为说明下面描述的概念的示例,我们将使用Enumerable.OrderBy聚合方法来排序包含浮点数小数数据字段的数据对象项。此浮点数数据字段将作为我们进行排序和对自定义数据集合中存储的项执行其他自定义聚合操作的键值。为了能够将 LINQ 聚合方法与自定义数据类型的数据对象项一起使用,我们实际上需要重写泛型委托比较器方法,这些方法执行两个数据对象项的比较。为此,我们需要从以下任一方法派生一个类:IComparable<T>或IComparer<T>并重新实现方法,提供允许对特定数据类型的数据对象项的值进行排序的自定义功能。有关以下方法的详细分步说明,请参阅本文的代码部分。
使用代码
假设我们有一个名为 Item
的类,它代表存储在特定自定义非泛型数据集合中的项。通常,此类包含分配给特定数据类型的单个值的字段,或操作所实现数据集合中每个特定项的方法。在这种情况下,我们声明了一个浮点数类型私有变量 m_value
。我们还提供了构造函数,用于在 Item
类对象被初始化时为 m_value
变量赋值。为了能够将每个 Item
对象与 LINQ 聚合函数一起使用,我们还应该从泛型 IComparable<T>
接口派生此类,并将类型参数 T 指定为 Item
类的值。该IComparable<T>接口仅包含一个名为 int CompareTo(T object)
的方法,该方法由 LINQ 聚合方法(如 OrderBy<T, TKey>, Average<T>, Max<T>, Min<T>
)调用,用于比较特定数据集合中的每个项。
通常,这个继承的方法会接受另一个 Item
对象值作为参数,并将其与当前数据对象项进行比较。通过比较两个数据对象项的结果,此方法返回一个值:如果当前项小于另一个比较项,则返回大于零的值;如果大于,则返回小于零的值;如果两个对象项相等,则返回零。在此类中实现的另一个方法是 float GetItem()
,用于检索由下面描述的值检索和排序方法使用的私有浮点数数据字段的值。
class Item : IComparable<Item>
{
private float m_value; // private float data field
public Item(float value) { m_value = value; }
public float GetItem() { return m_value; }
public int CompareTo(Item other)
{
// Comparing each item floating value using the following conditions
return (this.GetItem() != other.GetItem()) ?
((this.GetItem() < other.GetItem()) ? 1 : -1) : 0;
}
}
我们还需要实现的另一个非泛型类是 ItemsComparer
。此类派生自 IComparer<T>
接口,它封装了聚合方法调用所执行的每个集合数据对象项比较的基本机制。因此,在设计 ItemsComparer
类时,我们实际上是从 IComparer<T>
接口派生它,并且与前面的 Item
类实现类似,将类型参数 T 分配为 Item
类的值,这意味着从IComparer<T>派生的所有方法都将具有 Item
类类型的参数。与 Item
类类似,从 IComparer<T>
派生的类,ItemsComparer
类只能继承一个重载方法 int Compare(T x, T y)
。此方法提供了比较给定类型集合项对的基本功能。在这种情况下,此方法与前面提到的 IComparable<T>.CompareTo
的区别在于,调用此方法是为了比较存储在所设计数据集合中的整个对象项,而IComparable<T>.CompareTo(T other)用于比较 Item
数据对象项类中的特定数据字段。例如,如果IComparable<T>.CompareTo(T other)用于比较 Item
对象两个不同实例的两个浮点数值 m_value
成员变量,那么 IComparer<T>.Compare(T x, Ty)
将用于在所设计数据集合的 Item
数据对象类型项的对上执行相同的比较。IComparer<T>.Compare(T x, T y)
方法的返回值可以这样评估:如果 x 的值小于 y,则返回 -1;如果 x 的值大于 y,则返回 1;如果 x 和 y 都相等,则返回 0。在这种特定情况下,我们实际上并没有评估此方法的返回值,而是调用IComparer<T>.Compare(T x, T y)在 Item 类中实现的方法,并将其返回值分配给IComparable<T>.CompareTo该方法返回的值。因此,该值在这些方法外部返回,并在调用 Linq 聚合方法(如 Enumerable.OrderBy<TSource, TKey>)时被使用。IComparable<T>.CompareTomethod, 因此,此值从这些方法外部返回,并在调用 Linq 聚合方法(如 Enumerable.OrderBy<TSource, TKey>)时被使用。
class ItemsComparer : IComparer<Item>
{
public int Compare(Item x, Item y)
{
// Invoking a CompareTo method to compare each
// item x of the collection with the current item y specified
return x.CompareTo(y);
}
}
下面这部分 C# 代码实现了一个自定义的非泛型数据集合,该集合允许存储 Item 对象数据类型的数组,并提供添加和检索集合项的功能,或者实现 IEnumerable.GetEnumerator()
方法实例,这些实例允许获取集合的枚举器对象,该对象将由 C# foreach 语句进一步调用以迭代遍历存储在数据集合中的项。通常,为了在此 C# 代码示例中实现自定义非泛型数据集合,我们不会使用现有的泛型数据集合,如 List<T>, ArrayList
, Stack<T>
等。相反,我们基于使用 C# 简单数组实现集合,以存储 Item
数据类型内的多个数据对象。我们不从泛型集合派生 MyItemsCollection
类的原因是,它们已经实现了排序和其他聚合方法来操作存储在数据集合中的数据项。我们实际上是从“零”开始实现自定义非泛型集合,并且不需要任何这些泛型方法来重载。为了对存储在集合中的项进行排序,我们实现了 Sort()
方法,该方法调用 LINQ 的 Enumerable.OrderBy
子句。让我们回想一下,不仅泛型集合,如 List<T>
, ArrayList
,..., 而且 C# 中的简单数组也被视为派生自泛型 IEnumerable<T>
接口的对象,我们从中继承了 IEnumerable
接口以及其他泛型接口提供的所有方法。IEnumerable通常,我们将第一个和第二个类型参数传递给要排序的数据项的类型或将用于执行排序的键的类型的值,分别。
在这种情况下,这两个类型参数都分配给 Item
数据对象的值。该Enumerable.OrderBy方法的一个参数是 lambda 表达式,它用于指定将排序的特定值序列。该方法的下一个参数是 IComparer<T>
派生类的构造对象。此对象将进一步用于调用IComparer.Compare方法,该方法旨在执行两个对象项的实际比较。项目type.
class MyItemsCollection : IEnumerable<Item>
{
// The array of items of Item data type
private static Item[] m_Items = null;
// The array items index variable m_ItemIndex
private static int m_ItemIndex = 0;
public MyItemsCollection()
{
// Initializing the array of items
if (m_Items == null)
m_Items = new Item[0];
}
public void Add(Item item)
{
// Growing the size of the array
Array.Resize<Item>(ref m_Items, m_ItemIndex + 1);
// Assigning a value to the current item of m_Items array
// accessed by its m_ItemIndex index
m_Items[m_ItemIndex++] = item;
}
public void Sort()
{
// Invoking orderby LINQ aggregation method to sort
// the items stored in the array m_Items
m_Items = m_Items.OrderBy<Item, Item>(item => item,
new ItemsComparer()).ToArray();
}
public IEnumerator GetEnumerator()
{
// Retriving the value of the generic enumerator
// for the array m_Items
return m_Items.GetEnumerator();
}
IEnumerator<Item> IEnumerable<Item>.GetEnumerator()
{
// Invoking the GetEnumerator() methods of this object
// and assign its returning value to the returning value
// of this function explicitly casting it to the type of IEnumerator<Item>
return (IEnumerator<Item>)this.GetEnumerator();
}
}
最后,Program
类中实现的 C# 代码用于初始化 MyItemsCollection
类对象。初始化后,我们添加五个数据对象项,每个项都是上面描述的 Item
类的一个动态构造的对象。在这种情况下,浮点类型的值被分配给实例化每个 Item
类对象的构造函数参数。在集合初始化后,我们使用 C# foreach 语句遍历数据集合中的项,通过为每个数据项对象实例调用 GetItem()
方法来获取每个浮点值,并为检索到的每个浮点项的值执行控制台输出。
class Program
{
static void Main(string[] args)
{
// Initializing the custom collection's object
MyItemsCollection items = new MyItemsCollection();
// Adding items to the collection of data
items.Add(new Item(3.5F));
items.Add(new Item(0.58F));
items.Add(new Item(1.16F));
items.Add(new Item(0.32F));
items.Add(new Item(0.57F));
// Sorting the collection of data
items.Sort();
// Iterating through the collection of data constructed
// Retrieving items using GetItem() method and providing
// the console output for each item fetched from the current collection
foreach (Item item in items)
Console.WriteLine("{0:F}", item.GetItem());
Console.ReadKey();
}
}
关注点
本文介绍的编程方法可用于在 C#.NET 中实现自定义非泛型数据集合,以及使用 LINQ 聚合方法提供操作存储在自定义数据集合中的用户定义类型的数据对象项的功能。本文也适合初学者 C# 开发人员和企业家,作为自定义非泛型数据集合实现和其他 C# 编程语言中使用的面向对象编程技术基础的简要教程。
历史
- 2015 年 6 月 28 日 - 文章初稿发布