Using lambda expression in place of IComparer argument
Asked Answered
U

3

101

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

Unconformity answered 30/5, 2013 at 14:57 Comment(1)
Possible answer here: #9824935Pestana
F
72

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);
    }
}
Ferric answered 30/5, 2013 at 15:4 Comment(2)
Might want to give this class a different name to avoid conflicts with the library's class.Maltese
Syntactic detail: The constructor of a generic class must not include the <T> part of the class name.Weisbrodt
W
124

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)
    );
Weisbrodt answered 30/5, 2013 at 15:0 Comment(8)
Sadly we are languishing in .Net 3.5 land! Can't afford the mega-wedge needed to upgrade TFS to the latest version:-(Unconformity
@Unconformity if that is the only thing holding you back, have you considered dumping TFS in favor of something else?String
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?Poultry
@Poultry 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<TKey> delegate). You can create your own extension method if you want.Weisbrodt
The theory seems to be that the Interface has 2+ methods.Poultry
Now I want to use lambda in place of IEqualityComparer, but IEqualityComparer<T> doesn't have Create, why and how?Poultry
@Poultry 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<YourType> as the base class. You will need to implement only two abstract methods, and for that you get an object that is both IEqualityComparer<YourType> and IEqualityComparer (you do not have to specify these two interfaces, they come for free from the base class). Note that IEqualityComparer<in T> is contravariant in T, so your class can also be used as an IEqualityComparer<AnyDerivedYourType>.Weisbrodt
This question provide a solution #3190361. But why the SDK doesn't provide such a wrapper like Comparer<T>.Create?Poultry
F
72

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);
    }
}
Ferric answered 30/5, 2013 at 15:4 Comment(2)
Might want to give this class a different name to avoid conflicts with the library's class.Maltese
Syntactic detail: The constructor of a generic class must not include the <T> part of the class name.Weisbrodt
R
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.

Rhys answered 19/3, 2016 at 20:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.