使用 LINQ 对 NUnit 项目的集合类进行排序
提供了一个可以在 NUnit 项目中调用的静态类,用于对集合进行排序和比较。
引言
这是一个静态类,可用于根据对象的特定属性对自定义对象集合进行排序。这在需要比较两个相同类型且具有相同数量对象的集合时尤其有用,尤其当它们加载方式不同时。例如,在 NUnit 项目中,我们需要使用 Assert.AreEqual
来比较两个集合,一个集合是从数据库填充的,另一个集合是根据 XML 文件填充的,并且我们期望其值。此时,我们不知道对象是以何种顺序添加到集合中的,但两个集合包含相同数量的相同对象。当我们尝试比较它们时,由于对象顺序不同,测试会失败。使用 SortCollection
类,我们可以通过以相同方式对两个集合进行排序来消除这个问题。
必备组件
要运行本文提供的演示项目,您需要安装 Visual Studio 2008、Framework 3.5 和 NUnit(2.5 或更高版本)。
代码
我在本文中包含了一个演示项目,以展示 SortCollection
类如何使用。我将在文章后面讨论演示项目。首先,我想简要描述一下这个类的字段、方法和辅助方法。
在 Fields
区域,我们发现一个私有的枚举,它被 MinMaxItem
私有辅助方法项内部使用,以确定排序的类型:升序或降序。
Fields
区域包含一个私有的枚举
#region Fields
//===============================================
private enum MinMax { Min, Max };
//===============================================
#endregion
我们继续讲辅助方法区域,并讨论它们的方法和作用。第一个公开的方法是私有的 Sort
方法。返回类型是 IEnumerable<TSource>
。TSource
可以是任何类型的对象;在我的演示中,我使用了一个名为 Deal
的对象。Sort
方法使用泛型。第一个泛型类型 TSource
是我们集合中对象的类型,第二个泛型类型 TValue
可以是我们用于排序集合的任何值类型。TValue
基本上是对象中的一个属性。在我的示例中,我使用了 Amount
属性,它是一个 double
,但也可以是整数类型的 DealID
。Sort
方法有三个参数:第一个是我们要排序的集合,第二个是对 LINQ 在排序过程中内部使用的函数的委托,第三个是类型为 TValue
的比较器,它也是一个值类型。body
方法使用 LINQ 的 OrderBy
方法根据选择器和比较器对集合进行排序。返回类型是 LINQ 排序后的集合,稍后将使用 ConvertToCollection
方法将其转换为我们的 DealCollection
类型。
此区域的第二个方法是 MinMaxItem
,它返回一个 TSource
类型的对象。我的 MinMaxItem
方法与标准的 LINQ Max
方法的一个区别是,Max
方法返回集合中的最大值,而我的方法返回包含最大值的对象。例如,如果我们看演示项目,我想要找到集合中最大的 Amount
,如果我使用标准的 Max
方法,返回的值是 15000,但如果我使用我的方法,则返回包含 Amount
属性等于 15000 的对象。当,例如,在我们的 NUnit 测试中,一个测试通过处理交易来修改金额和/或其他属性时,这一点非常有用。稍后在另一个 NUnit 测试中,我们将交易金额和/或其他属性与测试交易对象属性进行比较,我们预先知道金额值或其他属性值应该是多少。与前面描述的方法相比,MinMaxitem
有一个额外的参数,即 MinMax
枚举类型。
#region Helpers // ======================================================
private static IEnumerable<TSource> Sort<TSource, TValue>(
this IEnumerable<TSource> source,
Func<TSource, TValue> selector,
IComparer<TValue> comparer)
{
try
{
IEnumerable<TSource> newSource =
source.OrderBy<TSource, TValue>(selector, comparer);
return newSource;
}
catch (System.Exception exception)
{ throw new FormatException(exception.ToString()); }
}
//===============================================================
private static TSource MinMaxItem<TSource, TValue>(
this IEnumerable<TSource> source,
Func<TSource, TValue> selector,
IComparer<TValue> comparer,
MinMax element)
{
try
{
TSource minMaxItem = default(TSource);
TValue minMaxValue = default(TValue);
using (var enumerator = source.GetEnumerator())
{
if (enumerator.MoveNext())
{
minMaxItem = enumerator.Current;
minMaxValue = selector(minMaxItem);
while (enumerator.MoveNext())
{
TValue value = selector(enumerator.Current);
if (element == MinMax.Max ?
comparer.Compare(value, minMaxValue) > 0 :
comparer.Compare(value, minMaxValue) < 0)
{
minMaxItem = enumerator.Current;
minMaxValue = value;
}
}
}
}
return minMaxItem;
}
catch (System.Exception exception)
{ throw new FormatException(exception.ToString()); }
}
//===============================================================
#endregion
Methods
区域公开了以下方法:MaxItem
、MinItem
、SortAscending
、SortDescending
和 ConvertToCollection
。让我们逐个讨论这些方法,从 MaxItem
开始。MaxItem
公共函数返回一个 TSource
类型,在我们的例子中是 Deal
类型。此方法调用 MinMaxItem
私有方法,该方法返回具有我们提供的属性最大值的对象。MinItem
也是一样,区别是该方法返回具有我们提供的属性最小值的对象。SortAscending
和 SortDescending
非常相似;后者使用 Reverse
方法反转集合的顺序。最后一个要讨论的方法是 ConverToCollection
方法,它是 public
的,因此可以在其他情况下使用。此方法将 IEnumarable
集合转换为自定义集合。
#region Methods
//=================================================================
public static TSource MaxItem<TSource, TValue>(this
IEnumerable<TSource> source, Func<TSource, TValue> selector)
{
return MinMaxItem<TSource, TValue>(source, selector,
Comparer<TValue>.Default, MinMax.Max);
}
//=================================================================
public static TSource MinItem<TSource, TValue>(this
IEnumerable<TSource> source, Func<TSource, TValue> selector)
{
return MinMaxItem<TSource, TValue>(source, selector,
Comparer<TValue>.Default, MinMax.Min);
}
//=================================================================
public static C SortAscending<TSource, TValue, C>(this
IEnumerable<TSource> source, Func<TSource, TValue> selector)
where C : IList<TSource>, new()
{
return ConvertToCollection<C, TSource>(Sort<TSource,
TValue>(source, selector, Comparer<TValue>.Default));
}
//=================================================================
public static C SortDescending<TSource, TValue, C>(this
IEnumerable<TSource> source, Func<TSource, TValue> selector)
where C : IList<TSource>, new()
{
return ConvertToCollection<C, TSource>(Sort<TSource, TValue>(source,
selector, Comparer<TValue>.Default).Reverse());
}
//=================================================================
public static C ConvertToCollection<C, T>(
this IEnumerable linqCollection)
where C : IList<T>, new()
{
C collection = new C();
//
foreach (var item in linqCollection)
{
collection.Add((T)item);
}
return collection;
}
//=================================================================
#endregion
使用代码
首先,我们需要看一下演示项目。让我们看看 SortedCollectionTest
NUnit 方法以及 SortAscending
方法是如何被调用的。正如我们所见,SortAscending
有三个泛型类型需要我们在调用时提供。第一个泛型类型是对象的类型;在我们的例子中,这个类型是 Deal
。下一个泛型类型是我们用来对集合进行排序的值类型;在我们的例子中是 double
。我在这里使用 double
是因为我想根据 Deal
对象是 double
的 Amount
属性来对集合进行排序。例如,如果我们按 DealID
对集合进行排序,我们也可以使用 int
。第三个泛型是 DealCollection
类型,它将在排序后用于将 IEnumerable
类型转换为 DealCollection
类型。该方法接受一个参数:选择器 (deal => deal.Amount),它用于指定要排序集合的属性及其类型。
像这样调用 SortAscending
方法
sortedCollA = dealCollectionA.SortAscending<Deal,
double, DealCollection>(deal => deal.Amount);
sortedCollB = dealCollectionB.SortAscending<Deal,
double, DealCollection>(deal => deal.Amount);
结论
总而言之,开发人员可以使用现有的 LINQ 方法,如 OrderBy
、OrderDescendingBy
和 Cast
等,来实现相同的结果。在我看来,使用这个静态类可能有一些潜在的优势,例如
- 对所有项目使用相同的类方法可提供一致性
- 无需在项目中包含
using Linq
指令即可对集合进行排序或提取 Min 或 Max,从而使 intellisense 更简洁 SortAscending
或SortDescending
会自动将IEnumerator
类型集合转换为指定类型的集合,并返回新类型的集合
关注点
这里主要关注的点是,有时我们无法控制需要比较的集合是如何填充的,我们需要能够以某种方式对它们进行排序。这只是对集合进行排序的另一种方式。
您觉得怎么样?
如果您喜欢这篇文章,请为它投票。如果您有任何问题,请随时提问,或者如果您认为有什么可以做得更好,请说出来。感谢您的时间,我希望这篇文章能为开发人员带来一些有用的东西。
历史
- V1.0: 2009/11/6