BlackWaspTM

This web site uses cookies. By using the site you accept the cookie policy.This message is for compliance with the UK ICO law.

LINQ
.NET 3.5+

Creating A Generic Lambda-Based IComparer<T>

Several standard .NET framework classes perform comparisons of values or objects and allow those comparisons to be controlled using an IComparer<T> implementation. This article describes a generic comparer that obtains results using lambda expressions.

IComparer<T>

In an earlier article I described a generic IEqualityComparer implementation that allowed you to create an equality comparer controlled using lambda expressions. Instances of this class could be used with Language-Integrated Query (LINQ) standard query operators, rather than creating custom comparer classes for each data type that you worked with. Several readers suggested that I write a similar article describing a generic, lambda-based implementation of the IComparer<T> interface.

IComparer<T> instances can be used with LINQ when using the OrderBy and OrderByDescending operators. With these LINQ extension methods a lambda expression is used to determine the member or expression that should be sorted and the IComparer is used to compare those members or calculated values. For example, the lambda expression may extract a string from the objects in the sequence being sorted and the comparer may determine how two items are ordered when they match except for a mix of upper and lower case or use of diacritics.

Although a generic IComparer has limited use with LINQ, it can be very useful with other methods provided by the .NET framework, many of which accept IComparer instances. Once we've created our generic comparer we'll use it to sort arrays without using LINQ or creating copies of the array.

LambdaComparer<T>

The comparer will use lambda expressions to perform comparisons, so a reasonable name for the class is LambdaComparer. To create the generic class and specify that it implements the IComparer<T> interface, add the following declaration:

public class LambdaComparer<T> : IComparer<T>
{
}

Injectable Lambda Expression

When we create a new instance of the LambdaComparer class we'll provide a lambda expression using constructor injection. This lambda expression will compare two values of the generic type and return an integer. The integer is used to specify which of the two values should appear first when sorted, or if the two values are equivalent. A negative result of any magnitude indicates that the first number is the smaller, or appears first when sorted. A positive result specifies that the first number is larger, or occurs later when ordered. If the two values are regarded as the same, the function should return zero.

To create the field that will hold the lambda expression, add the following Func delegate to the class. Here the delegate is declared as read-only, as there is no need to change its value outside of the constructor. Note that the two input parameters for the delegate receive values of our generic type, "T", and that the return value is an integer.

readonly Func<T, T, int> _compareFunction;

We can now add the constructor, which initialises the Func delegate field:

public LambdaComparer(Func<T, T, int> compareFunction)
{
    _compareFunction = compareFunction;
}

Implementing the IComparer<T> Interface

To complete the class we need to implement the single method of the IComparer<T> interface. This is the Compare method. It accepts the two values to compare and returns an integer. For the LambdaComparer class we'll call the Func delegate injected using the constructor and return the result.

public int Compare(T item1, T item2)
{
    return _compareFunction(item1, item2);
}

Testing the LambdaComparer

We can now try out the comparer. It can be used with data of any type so we'll create a simple class to work with. The Triplet class below holds three integer values.

public class Triplet
{
    public int A { get; set; }
    public int B { get; set; }
    public int C { get; set; }

    public override string ToString()
    {
        return string.Format("[{0},{1},{2}]", A, B, C);
    }
}

We'll now create an array of Triplets that we will later sort. Add the following to the Main method of your program.

Triplet[] triplets = new Triplet[]
{
    new Triplet { A = 1, B = 8, C = 1 },
    new Triplet { A = 3, B = 4, C = 6 },
    new Triplet { A = 5, B = 2, C = 2 },
    new Triplet { A = 7, B = 6, C = 7 },
    new Triplet { A = 2, B = 5, C = 9 },
    new Triplet { A = 4, B = 3, C = 5 },
    new Triplet { A = 6, B = 7, C = 9,},
    new Triplet { A = 8, B = 1, C = 3 }
};

Now we can sort the array in place using the Array.Sort method. For the first example we'll sort only on the "A" property of the Triplets. To do so we'll use a lambda expression that subtracts the second value's "A" property from the same member of the first Triplet. This subtraction operation will produce a negative, positive or zero result depending upon the order that they should appear in.

var comparer = new LambdaComparer<Triplet>((x, y) => x.A - y.A);
Array.Sort(triplets, comparer);

foreach (Triplet t in triplets)
{
    Console.WriteLine(t);
}

/* OUTPUT

[1,8,1]
[2,5,9]
[3,4,6]
[4,3,5]
[5,2,2]
[6,7,9]
[7,6,7]
[8,1,3]

*/

The next example shows a slightly more complex ordering operation where the sorting is based upon the product of the three numeric properties of each triplet.

var comparer = new LambdaComparer<Triplet>(
    (x, y) => (x.A + x.B + x.C) - (y.A + y.B + y.C));

/* OUTPUT

[5,2,2]
[1,8,1]
[4,3,5]
[8,1,3]
[3,4,6]
[2,5,9]
[7,6,7]
[6,7,9]

*/
4 June 2012