Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I specify my explicit type comparator inline?

Tags:

c#

.net

lambda

linq

So .NET 3.0/3.5 provides us with lots of new ways to query, sort, and manipulate data, thanks to all the neat functions supplied with LINQ. Sometimes, I need to compare user-defined types that don't have a built-in comparison operator. In many cases, the comparison is really simple -- something like foo1.key ?= foo2.key. Rather than creating a new IEqualityComparer for the type, can I simply specify the comparison inline using anonymous delegates/lambda functions? Something like:

var f1 = ...,     f2 = ...; var f3 = f1.Except(            f2, new IEqualityComparer(              (Foo a, Foo b) => a.key.CompareTo(b.key)            ) ); 

I'm pretty sure the above doesn't actually work. I just don't want to have to make something as "heavy" as a whole class just to tell the program how to compare apples to apples.

like image 501
Coderer Avatar asked Oct 09 '08 16:10

Coderer


1 Answers

My MiscUtil library contains a ProjectionComparer to build an IComparer<T> from a projection delegate. It would be the work of 10 minutes to make a ProjectionEqualityComparer to do the same thing.

EDIT: Here's the code for ProjectionEqualityComparer:

using System; using System.Collections.Generic;  /// <summary> /// Non-generic class to produce instances of the generic class, /// optionally using type inference. /// </summary> public static class ProjectionEqualityComparer {     /// <summary>     /// Creates an instance of ProjectionEqualityComparer using the specified projection.     /// </summary>     /// <typeparam name="TSource">Type parameter for the elements to be compared</typeparam>     /// <typeparam name="TKey">Type parameter for the keys to be compared,     /// after being projected from the elements</typeparam>     /// <param name="projection">Projection to use when determining the key of an element</param>     /// <returns>A comparer which will compare elements by projecting      /// each element to its key, and comparing keys</returns>     public static ProjectionEqualityComparer<TSource, TKey> Create<TSource, TKey>(Func<TSource, TKey> projection)     {         return new ProjectionEqualityComparer<TSource, TKey>(projection);     }      /// <summary>     /// Creates an instance of ProjectionEqualityComparer using the specified projection.     /// The ignored parameter is solely present to aid type inference.     /// </summary>     /// <typeparam name="TSource">Type parameter for the elements to be compared</typeparam>     /// <typeparam name="TKey">Type parameter for the keys to be compared,     /// after being projected from the elements</typeparam>     /// <param name="ignored">Value is ignored - type may be used by type inference</param>     /// <param name="projection">Projection to use when determining the key of an element</param>     /// <returns>A comparer which will compare elements by projecting     /// each element to its key, and comparing keys</returns>     public static ProjectionEqualityComparer<TSource, TKey> Create<TSource, TKey>         (TSource ignored,          Func<TSource, TKey> projection)     {         return new ProjectionEqualityComparer<TSource, TKey>(projection);     }  }  /// <summary> /// Class generic in the source only to produce instances of the  /// doubly generic class, optionally using type inference. /// </summary> public static class ProjectionEqualityComparer<TSource> {     /// <summary>     /// Creates an instance of ProjectionEqualityComparer using the specified projection.     /// </summary>     /// <typeparam name="TKey">Type parameter for the keys to be compared,     /// after being projected from the elements</typeparam>     /// <param name="projection">Projection to use when determining the key of an element</param>     /// <returns>A comparer which will compare elements by projecting each element to its key,     /// and comparing keys</returns>             public static ProjectionEqualityComparer<TSource, TKey> Create<TKey>(Func<TSource, TKey> projection)     {         return new ProjectionEqualityComparer<TSource, TKey>(projection);     } }  /// <summary> /// Comparer which projects each element of the comparison to a key, and then compares /// those keys using the specified (or default) comparer for the key type. /// </summary> /// <typeparam name="TSource">Type of elements which this comparer  /// will be asked to compare</typeparam> /// <typeparam name="TKey">Type of the key projected /// from the element</typeparam> public class ProjectionEqualityComparer<TSource, TKey> : IEqualityComparer<TSource> {     readonly Func<TSource, TKey> projection;     readonly IEqualityComparer<TKey> comparer;      /// <summary>     /// Creates a new instance using the specified projection, which must not be null.     /// The default comparer for the projected type is used.     /// </summary>     /// <param name="projection">Projection to use during comparisons</param>     public ProjectionEqualityComparer(Func<TSource, TKey> projection)         : this(projection, null)     {     }      /// <summary>     /// Creates a new instance using the specified projection, which must not be null.     /// </summary>     /// <param name="projection">Projection to use during comparisons</param>     /// <param name="comparer">The comparer to use on the keys. May be null, in     /// which case the default comparer will be used.</param>     public ProjectionEqualityComparer(Func<TSource, TKey> projection, IEqualityComparer<TKey> comparer)     {         if (projection == null)         {             throw new ArgumentNullException("projection");         }         this.comparer = comparer ?? EqualityComparer<TKey>.Default;         this.projection = projection;     }      /// <summary>     /// Compares the two specified values for equality by applying the projection     /// to each value and then using the equality comparer on the resulting keys. Null     /// references are never passed to the projection.     /// </summary>     public bool Equals(TSource x, TSource y)     {         if (x == null && y == null)         {             return true;         }         if (x == null || y == null)         {             return false;         }         return comparer.Equals(projection(x), projection(y));     }      /// <summary>     /// Produces a hash code for the given value by projecting it and     /// then asking the equality comparer to find the hash code of     /// the resulting key.     /// </summary>     public int GetHashCode(TSource obj)     {         if (obj == null)         {             throw new ArgumentNullException("obj");         }         return comparer.GetHashCode(projection(obj));     } } 

And here's a sample use:

var f3 = f1.Except(f2, ProjectionEqualityComparer<Foo>.Create(a => a.key)); 
like image 96
Jon Skeet Avatar answered Oct 13 '22 21:10

Jon Skeet