DistinctBy in Linq (Find Distinct object by Property)

Linq-Distinct-Screenshot1

public class Size {
   public string Name { get; set; }
   public int Height { get; set; }
   public int Width { get; set; }
}

Consider that we have Size Class for advertisement which is having Heigth, Width and Name as property in it. Now Requirement is I have to find out the all product with distinct Name values.

Size[] adv = { 
    new Size { Name = "Leaderboard", Height = 90, Width = 728 }, 
    new Size { Name = "Large Rectangle", Height = 280, Width = 336 }, 
    new Size { Name = "Large Mobile Banner", Height = 100, Width = 320 }, 
    new Size { Name = "Large Skyscraper", Height = 600, Width = 300 },
    new Size { Name = "Medium Rectangle", Height = 250, Width = 300 },
    new Size { Name = "Large Skyscraper", Height = 300, Width = 600 },
};

var lst = adv.Distinct();

foreach (Size p in lst) {
   Console.WriteLine(p.Height + " : " + p.Name);
}

It returns all the size event though two size have same Name value. So this doesn’t meet requirement of getting object with distinct Name value.

Way 1: Implement Comparable

First way to achieve the same functionality is make use of overload Distinct function which support to have comparator as argument.

Here is MSDN documentation on this : Enumerable.Distinct<TSource> Method (IEnumerable<TSource>, IEqualityComparer<TSource>).

So for that I implemented IEqualityComparer and created new SizeComparare which you can see in below code.

class SizeComparare : IEqualityComparer<Size> {
   private Func<Size, object> _funcDistinct;
   public SizeComparare(Func<Size, object> funcDistinct) {
      this._funcDistinct = funcDistinct;
   }

   public bool Equals(Size x, Size y) {
      return _funcDistinct(x).Equals(_funcDistinct(y));
   }

   public int GetHashCode(Size obj) {
      return this._funcDistinct(obj).GetHashCode();
   }
}

So in SizeComparare constructor I am passing function as argument, so when I create any object of it I have to pass my project function as argument. In Equal method I am comparing object which are returned by my projection function. Now following is the way how I used this Comparare implementation to satisfy my requirement.

Way 2: Implement Comparable

The second and most eaisest way to avoide this I did in above like using Comparare implementation is just make use of GroupBy like as below

List<Size> list = adv
                     .GroupBy(a => a.Name)
                     //.GroupBy(a => new { a.Name, a.Width })
                     .Select(g => g.First())
                     .ToList();

foreach (Size p in list) {
   Console.WriteLine(p.Height + "x" + p.Width + " : " + p.Name);
}

So this approach also satisfy my requirement easily and output is similar to above two approach. If you want to pass more than on property than you can just do like this .GroupBy(a => new { a.Width, a.Code }).

Linq-Distinct-Screenshot2

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;
using System.Threading;

namespace DistinctBy {
   public class Size {
      public string Name { get; set; }
      public int Height { get; set; }
      public int Width { get; set; }
   }

   class SizeComparare : IEqualityComparer<Size> {
      private Func<Size, object> _funcDistinct;
      public SizeComparare(Func<Size, object> funcDistinct) {
         this._funcDistinct = funcDistinct;
      }

      public bool Equals(Size x, Size y) {
         return _funcDistinct(x).Equals(_funcDistinct(y));
      }

      public int GetHashCode(Size obj) {
         return this._funcDistinct(obj).GetHashCode();
      }
   }

   class Program {
      static void Main(string[] args) {

         Console.WriteLine("Linq Distinct");
         Size[] adv = { 
                        new Size { Name = "Leaderboard", Height = 90, Width = 728 }, 
                        new Size { Name = "Large Rectangle", Height = 280, Width = 336 }, 
                        new Size { Name = "Large Mobile Banner", Height = 100, Width = 320}, 
                        new Size { Name = "Large Skyscraper", Height = 600, Width = 300 },
                        new Size { Name = "Medium Rectangle", Height = 250, Width = 300},
                        new Size { Name = "Large Skyscraper", Height = 300, Width = 600 },
                      };

         var lst = adv.Distinct();

         foreach (Size p in lst) {
            Console.WriteLine(p.Height + "x" + p.Width + " : " + p.Name);
         }

         Console.WriteLine("\nCompare Distinct");

         var list2 = adv.Distinct(new SizeComparare(a => new { a.Name, a.Height }));
         //var list2 = adv.Distinct(new SizeComparare(a => a.Name));

         foreach (Size p in list2) {
            Console.WriteLine(p.Height + "x" + p.Width + " : " + p.Name);
         }

         Console.WriteLine("\nGroup By way");
         List<Size> list = adv
                              .GroupBy(a => a.Name)
                              //.GroupBy(a => new { a.Name, a.Width })
                              .Select(g => g.First())
                              .ToList();

         foreach (Size p in list) {
            Console.WriteLine(p.Height + "x" + p.Width + " : " + p.Name);
         }

         Console.ReadLine();
      }
   }
}

Happy coding!

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.