Introduction
This is a static class that can be used to sort a custom collection of objects based on a specific attribute of an object. This is useful especially when two collections of the same type with the same number of objects are loaded differently and need to be compared. For example, on a NUnit project, we need to Assert.AreEqual
two collections, one populated from the database and the other populated based from an XML file with values that are expected. At this moment in time, we don't know in which order the objects are added to the collection, but the same number and same objects are part of both collections. When we are trying to compare them, the test fails because the objects are not in the same order. Using the SortCollection
class, we can eliminate this problem by sorting both collections in the same way.
To run the demo project supplied with this article, you will need to have Visual Studio 2008, Framework 3.5, and NUnit (2.5 or later) installed.
The Code
I have included in this article a demo project to see how the SortCollection
class can be used. I will discuss the demo project later in the article. First, I would like to describe a little bit about the fields, methods, and helper methods of this class.
In the Fields
region, we find a private enumeration which is used internally by the MinMaxItem
private helper method item to determine the type of sorting: ascending or descending.
The Fields
region contains a private enumeration:
#region Fields
private enum MinMax { Min, Max };
#endregion
Let's carry on with the helper methods region, and talk about the methods and what they achieve. The first method to expose is the private Sort
method. The retuning type is an IEnumerable<TSource>
. TSource
can be any type of object; in my demo, I used an object called Deal
. The Sort
method uses Generics. The first generic type TSource
is the type of object in our collection, and the second generic type TValue
can by any value type that we want to use for sorting the collection. TValue
is basically a property in the object. In my example, I used the Amount
property which is a double
, but also can be DealID
which is an integer type. The Sort
method has three arguments: first is our collection to sort, the second is the delegate to a function used internally by LINQ in the sorting process, and the third one is the comparer of type TValue
, which again is a value type. The body
method uses the LINQ method OrderBy
to sort the collection based on the selector and comparer. The returning type is a LINQ sorted collection which will be converted later on in our DealCollection
type using the ConvertToCollection
method.
The second method in this region is MinMaxItem
, which returns an object of type TSource
. One difference between the standard LINQ Max
method and the MinMaxItem
method is, the Max
method returns the maximum value in the collection, whereas my method returns the object which contains the maximum value. For example, if we have a look at the demo project, I want to find the maximum Amount
in my collection, and if I use the standard Max
method, the value returned is 15000, but if I use my method, then the object which has the Amount
property equal to 15000 is returned. This is very useful when, for example, in our NUnit tests, one test is modifying the amount and/or other properties by processing the deal. Later in another NUnit test, we compare the deal amount and/or other properties against the test deal object properties, where we know in advance what the amount value or other property values should be. MinMaxitem
has an extra argument compared with the previously described method, which is the MinMax
enumeration type.
#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
The region Methods
expose the following: MaxItem
, MinItem
, SortAscending
, SortDescending
, and ConvertToCollection
. Let's talk about every method, starting with MaxItem
. The MaxItem
public function has a returning type of TSource
, which in our case is of type Deal
. This method calls the MinMaxItem
private method, which returns the object having the maximum value of the property we are supplying. Same with MinItem
, the difference is the method returns the object having the minimum value of the property we are supplying. SortAscending
and SortDescending
are pretty similar; the latter uses the Reverse
method to reverse the order of the collection. The last method to discuss here is the ConverToCollection
method which is public
, so it can be used in other situations. This method converts an IEnumarable
collection into a custom collection.
#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
Using the code
First, we need to look at the demo project. Let's have a look at the SortedCollectionTest
NUnit method and how the SortAscending
method is called. As we can see, SortAscending
has three generic types that we need to supply when we call it. The first generic type is the type of the object; in our case, this type is Deal
. The next generic type is the value type that we'll use to sort the collection; in our case, double
. I use double
here because I want to sort the collection based on the Amount
property of the Deal
object, which is a double
. We also can use, for example, int
if we are going to sort the collection after DealID
. The third generic is the DealCollection
type, and it will be used in the conversion of the IEnumerable
type into the DealCollection
type after sorting. The method takes one argument: the selector (deal => deal.Amount) which is used to specify the property and its type to sort the collection.
Call the SortAscending
method like this:
sortedCollA = dealCollectionA.SortAscending<Deal,
double, DealCollection>(deal => deal.Amount);
sortedCollB = dealCollectionB.SortAscending<Deal,
double, DealCollection>(deal => deal.Amount);
Conclusion
In conclusion, the developer can use existing LINQ methods like OrderBy
, OrderDescendingBy
, and Cast
etc., to achieve the same result. In my opinion, using this static class can have some potential advantages like:
- Using the same class methods for all the projects provides consistency
- No need for including the
using Linq
directive in projects to be able to sort a collection or extract Min or Max, keeping intellisense cleaner SortAscending
or SortDescending
automatically converts an IEnumerator
type collection into a specified type collection, and returns the new type of collection
Points of Interest
The main point of interest here is that sometimes we have no control of how collections that need to be compared are populated, and we need to be able to sort them in a way. This is just another way of sorting a collection.
If you liked the article, please vote for it. If you have any questions, please don't hesitate to ask, or if you think that something can be done in a better way, please say it. Thanks for your time, and I hope this article brings something useful to developers.
History