Can you create a simple 'EqualityComparer' using a lambda expression

前端 未结 8 1464
半阙折子戏
半阙折子戏 2020-12-13 18:20

Short question:

Is there a simple way in LINQ to objects to get a distinct list of objects from a list based on a key property on the objects.

相关标签:
8条回答
  • 2020-12-13 19:00

    (There are two solutions here - see the end for the second one):

    My MiscUtil library has a ProjectionEqualityComparer class (and two supporting classes to make use of type inference).

    Here's an example of using it:

    EqualityComparer<GalleryImage> comparer = 
        ProjectionEqualityComparer<GalleryImage>.Create(x => x.id);
    

    Here's the code (comments removed)

    // Helper class for construction
    public static class ProjectionEqualityComparer
    {
        public static ProjectionEqualityComparer<TSource, TKey>
            Create<TSource, TKey>(Func<TSource, TKey> projection)
        {
            return new ProjectionEqualityComparer<TSource, TKey>(projection);
        }
    
        public static ProjectionEqualityComparer<TSource, TKey>
            Create<TSource, TKey> (TSource ignored,
                                   Func<TSource, TKey> projection)
        {
            return new ProjectionEqualityComparer<TSource, TKey>(projection);
        }
    }
    
    public static class ProjectionEqualityComparer<TSource>
    {
        public static ProjectionEqualityComparer<TSource, TKey>
            Create<TKey>(Func<TSource, TKey> projection)
        {
            return new ProjectionEqualityComparer<TSource, TKey>(projection);
        }
    }
    
    public class ProjectionEqualityComparer<TSource, TKey>
        : IEqualityComparer<TSource>
    {
        readonly Func<TSource, TKey> projection;
        readonly IEqualityComparer<TKey> comparer;
    
        public ProjectionEqualityComparer(Func<TSource, TKey> projection)
            : this(projection, null)
        {
        }
    
        public ProjectionEqualityComparer(
            Func<TSource, TKey> projection,
            IEqualityComparer<TKey> comparer)
        {
            projection.ThrowIfNull("projection");
            this.comparer = comparer ?? EqualityComparer<TKey>.Default;
            this.projection = projection;
        }
    
        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));
        }
    
        public int GetHashCode(TSource obj)
        {
            if (obj == null)
            {
                throw new ArgumentNullException("obj");
            }
            return comparer.GetHashCode(projection(obj));
        }
    }
    

    Second solution

    To do this just for Distinct, you can use the DistinctBy extension in MoreLINQ:

        public static IEnumerable<TSource> DistinctBy<TSource, TKey>
            (this IEnumerable<TSource> source,
             Func<TSource, TKey> keySelector)
        {
            return source.DistinctBy(keySelector, null);
        }
    
        public static IEnumerable<TSource> DistinctBy<TSource, TKey>
            (this IEnumerable<TSource> source,
             Func<TSource, TKey> keySelector,
             IEqualityComparer<TKey> comparer)
        {
            source.ThrowIfNull("source");
            keySelector.ThrowIfNull("keySelector");
            return DistinctByImpl(source, keySelector, comparer);
        }
    
        private static IEnumerable<TSource> DistinctByImpl<TSource, TKey>
            (IEnumerable<TSource> source,
             Func<TSource, TKey> keySelector,
             IEqualityComparer<TKey> comparer)
        {
            HashSet<TKey> knownKeys = new HashSet<TKey>(comparer);
            foreach (TSource element in source)
            {
                if (knownKeys.Add(keySelector(element)))
                {
                    yield return element;
                }
            }
        }
    

    In both cases, ThrowIfNull looks like this:

    public static void ThrowIfNull<T>(this T data, string name) where T : class
    {
        if (data == null)
        {
            throw new ArgumentNullException(name);
        }
    }
    
    0 讨论(0)
  • 2020-12-13 19:04

    Here's an interesting article that extends LINQ for this purpose... http://www.singingeels.com/Articles/Extending_LINQ__Specifying_a_Property_in_the_Distinct_Function.aspx

    The default Distinct compares objects based on their hashcode - to easily make your objects work with Distinct, you could override the GetHashcode method.. but you mentioned that you are retrieving your objects from a web service, so you may not be able to do that in this case.

    0 讨论(0)
提交回复
热议问题