Using lambda expression in place of IComparer argument
Solution 1
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);
}
}
Solution 2
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)
);
Solution 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.
haughtonomous
Updated on May 24, 2021Comments
-
haughtonomous about 3 years
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
-
Servy almost 11 yearsMight want to give this class a different name to avoid conflicts with the library's class.
-
Jeppe Stig Nielsen almost 11 yearsSyntactic detail: The constructor of a generic class must not include the
<T>
part of the class name. -
haughtonomous almost 11 yearsSadly we are languishing in .Net 3.5 land! Can't afford the mega-wedge needed to upgrade TFS to the latest version:-(
-
Arturo Hernandez about 10 years@haughtonomous if that is the only thing holding you back, have you considered dumping TFS in favor of something else?
-
jw_ over 4 yearsDo 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?
-
Jeppe Stig Nielsen over 4 years@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 aComparison<TKey>
delegate). You can create your own extension method if you want. -
jw_ over 4 yearsThe theory seems to be that the Interface has 2+ methods.
-
jw_ over 4 yearsNow I want to use lambda in place of IEqualityComparer, but IEqualityComparer<T> doesn't have Create, why and how?
-
Jeppe Stig Nielsen over 4 years@jw_ You need two methods,
Equals
andGetHashCode
. There is no method that lets you do this from two delegates (lambdas). Instead, create your own class and useEqualityComparer<YourType>
as the base class. You will need to implement only two abstract methods, and for that you get an object that is bothIEqualityComparer<YourType>
andIEqualityComparer
(you do not have to specify these two interfaces, they come for free from the base class). Note thatIEqualityComparer<in T>
is contravariant inT
, so your class can also be used as anIEqualityComparer<AnyDerivedYourType>
. -
jw_ over 4 yearsThis question provide a solution stackoverflow.com/questions/3189861/…. But why the SDK doesn't provide such a wrapper like Comparer<T>.Create?