100

Is it possible with C# to pass a lambda expression as an IComparer argument in a method call?

eg something like

var x = someIEnumerable.OrderBy(aClass e => e.someProperty, 
(aClass x, aClass y) => 
  x.someProperty > y.SomeProperty ?  1 : x.someProperty < y.SomeProperty ?  -1 : 0);

I can't quite get this to compile so I'm guessing not, but it seems such an obvious synergy between lambdas and anonymous delegates that I feel I must be doing something foolishly wrong.

TIA

Douglas
  • 53,759
  • 13
  • 140
  • 188
haughtonomous
  • 4,602
  • 11
  • 34
  • 52

3 Answers3

123

If you're on .NET 4.5, you can use the static method Comparer<aClass>.Create.

Documentation: Comparer<T>.Create Method .

Example:

var x = someIEnumerable.OrderBy(e => e.someProperty, 
    Comparer<aClass>.Create((x, y) => x.someProperty > y.SomeProperty ?  1 : x.someProperty < y.SomeProperty ?  -1 : 0)
    );
Dmitriy
  • 3,305
  • 7
  • 44
  • 55
Jeppe Stig Nielsen
  • 60,409
  • 11
  • 110
  • 181
  • 1
    Sadly we are languishing in .Net 3.5 land! Can't afford the mega-wedge needed to upgrade TFS to the latest version:-( – haughtonomous May 30 '13 at 15:27
  • 3
    @haughtonomous if that is the only thing holding you back, have you considered dumping TFS in favor of something else? – Arturo Hernandez Mar 06 '14 at 15:43
  • Do you know the essential theory (not like "since it reqire type other than a lambda") about why we can't put lambda directly there, but need a wrapper? – jw_ Dec 02 '19 at 03:59
  • @jw_ I am not sure how much theory there is behind this. The authors of `.OrderBy` (Linq) decided not to have an overload that accepted a delegate for the comparison (like a `Comparison` delegate). You can create your own extension method if you want. – Jeppe Stig Nielsen Dec 02 '19 at 07:38
  • The theory seems to be that the Interface has 2+ methods. – jw_ Dec 04 '19 at 09:01
  • Now I want to use lambda in place of IEqualityComparer, but IEqualityComparer doesn't have Create, why and how? – jw_ Dec 04 '19 at 09:02
  • @jw_ You need two methods, `Equals` and `GetHashCode`. There is no method that lets you do this from two delegates (lambdas). Instead, create your own class and use `EqualityComparer` as the base class. You will need to implement only two abstract methods, and for that you get an object that is both `IEqualityComparer` and `IEqualityComparer` (you do not have to specify these two interfaces, they come for free from the base class). Note that `IEqualityComparer` is __contravariant__ in `T`, so your class can also be used as an `IEqualityComparer`. – Jeppe Stig Nielsen Dec 04 '19 at 09:10
  • This question provide a solution https://stackoverflow.com/questions/3189861/pass-a-lambda-expression-in-place-of-icomparer-or-iequalitycomparer-or-any-singl. But why the SDK doesn't provide such a wrapper like Comparer.Create? – jw_ Dec 04 '19 at 09:30
71

As Jeppe points out, if you're on .NET 4.5, you can use the static method Comparer<T>.Create.

If not, this is an implementation that should be equivalent:

public class FunctionalComparer<T> : IComparer<T>
{
    private Func<T, T, int> comparer;
    public FunctionalComparer(Func<T, T, int> comparer)
    {
        this.comparer = comparer;
    }
    public static IComparer<T> Create(Func<T, T, int> comparer)
    {
        return new FunctionalComparer<T>(comparer);
    }
    public int Compare(T x, T y)
    {
        return comparer(x, y);
    }
}
Nicholas Petersen
  • 9,104
  • 7
  • 59
  • 69
Timothy Shields
  • 75,459
  • 18
  • 120
  • 173
3

If you consistently want to compare projected keys (such as a single property), you can define a class that encapsulates all the key comparison logic for you, including null checks, key extraction on both objects, and key comparison using the specified or default inner comparer:

public class KeyComparer<TSource, TKey> : Comparer<TSource>
{
    private readonly Func<TSource, TKey> _keySelector;
    private readonly IComparer<TKey> _innerComparer;

    public KeyComparer(
        Func<TSource, TKey> keySelector, 
        IComparer<TKey> innerComparer = null)
    {
        _keySelector = keySelector;
        _innerComparer = innerComparer ?? Comparer<TKey>.Default;
    }

    public override int Compare(TSource x, TSource y)
    {
        if (object.ReferenceEquals(x, y))
            return 0;
        if (x == null)
            return -1;
        if (y == null)
            return 1;

        TKey xKey = _keySelector(x);
        TKey yKey = _keySelector(y);
        return _innerComparer.Compare(xKey, yKey);
    }
}

For convenience, a factory method:

public static class KeyComparer
{
    public static KeyComparer<TSource, TKey> Create<TSource, TKey>(
        Func<TSource, TKey> keySelector, 
        IComparer<TKey> innerComparer = null)
    {
        return new KeyComparer<TSource, TKey>(keySelector, innerComparer);
    }
}

You could then use this like so:

var sortedSet = new SortedSet<MyClass>(KeyComparer.Create((MyClass o) => o.MyProperty));

You can refer to my blog post for an expanded discussion of this implementation.

Douglas
  • 53,759
  • 13
  • 140
  • 188