Distinct() with lambda?

C#C# 3.0LambdaExtension Methods

C# Problem Overview


Right, so I have an enumerable and wish to get distinct values from it.

Using System.Linq, there's, of course, an extension method called Distinct. In the simple case, it can be used with no parameters, like:

var distinctValues = myStringList.Distinct();

Well and good, but if I have an enumerable of objects for which I need to specify equality, the only available overload is:

var distinctValues = myCustomerList.Distinct(someEqualityComparer);

The equality comparer argument must be an instance of IEqualityComparer<T>. I can do this, of course, but it's somewhat verbose and, well, cludgy.

What I would have expected is an overload that would take a lambda, say a Func<T, T, bool>:

var distinctValues = myCustomerList.Distinct((c1, c2) => c1.CustomerId == c2.CustomerId);

Anyone know if some such extension exists, or some equivalent workaround? Or am I missing something?

Alternatively, is there a way of specifying an IEqualityComparer inline (embarrass me)?

Update

I found a reply by Anders Hejlsberg to a post in an MSDN forum on this subject. He says:

> The problem you're going to run into is that when two objects compare > equal they must have the same GetHashCode return value (or else the > hash table used internally by Distinct will not function correctly). > We use IEqualityComparer because it packages compatible > implementations of Equals and GetHashCode into a single interface.

I suppose that makes sense.

C# Solutions


Solution 1 - C#

IEnumerable<Customer> filteredList = originalList
  .GroupBy(customer => customer.CustomerId)
  .Select(group => group.First());

Solution 2 - C#

It looks to me like you want DistinctBy from MoreLINQ. You can then write:

var distinctValues = myCustomerList.DistinctBy(c => c.CustomerId);

Here's a cut-down version of DistinctBy (no nullity checking and no option to specify your own key comparer):

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

Solution 3 - C#

To Wrap things up . I think most of the people which came here like me want the simplest solution possible without using any libraries and with best possible performance.

(The accepted group by method for me i think is an overkill in terms of performance. )

Here is a simple extension method using the IEqualityComparer interface which works also for null values.

Usage:

var filtered = taskList.DistinctBy(t => t.TaskExternalId).ToArray();

Extension Method Code

public static class LinqExtensions
{
    public static IEnumerable<T> DistinctBy<T, TKey>(this IEnumerable<T> items, Func<T, TKey> property)
    {
        GeneralPropertyComparer<T, TKey> comparer = new GeneralPropertyComparer<T,TKey>(property);
        return items.Distinct(comparer);
    }   
}
public class GeneralPropertyComparer<T,TKey> : IEqualityComparer<T>
{
    private Func<T, TKey> expr { get; set; }
    public GeneralPropertyComparer (Func<T, TKey> expr)
    {
        this.expr = expr;
    }
    public bool Equals(T left, T right)
    {
        var leftProp = expr.Invoke(left);
        var rightProp = expr.Invoke(right);
        if (leftProp == null && rightProp == null)
            return true;
        else if (leftProp == null ^ rightProp == null)
            return false;
        else
            return leftProp.Equals(rightProp);
    }
    public int GetHashCode(T obj)
    {
        var prop = expr.Invoke(obj);
        return (prop==null)? 0:prop.GetHashCode();
    }
}

Solution 4 - C#

Shorthand solution

myCustomerList.GroupBy(c => c.CustomerId, (key, c) => c.FirstOrDefault());

Solution 5 - C#

No there is no such extension method overload for this. I've found this frustrating myself in the past and as such I usually write a helper class to deal with this problem. The goal is to convert a Func<T,T,bool> to IEqualityComparer<T,T>.

Example

public class EqualityFactory {
  private sealed class Impl<T> : IEqualityComparer<T,T> {
    private Func<T,T,bool> m_del;
    private IEqualityComparer<T> m_comp;
    public Impl(Func<T,T,bool> del) { 
      m_del = del;
      m_comp = EqualityComparer<T>.Default;
    }
    public bool Equals(T left, T right) {
      return m_del(left, right);
    } 
    public int GetHashCode(T value) {
      return m_comp.GetHashCode(value);
    }
  }
  public static IEqualityComparer<T,T> Create<T>(Func<T,T,bool> del) {
    return new Impl<T>(del);
  }
}

This allows you to write the following

var distinctValues = myCustomerList
  .Distinct(EqualityFactory.Create((c1, c2) => c1.CustomerId == c2.CustomerId));

Solution 6 - C#

Here's a simple extension method that does what I need...

public static class EnumerableExtensions
{
    public static IEnumerable<TKey> Distinct<T, TKey>(this IEnumerable<T> source, Func<T, TKey> selector)
    {
        return source.GroupBy(selector).Select(x => x.Key);
    }
}

It's a shame they didn't bake a distinct method like this into the framework, but hey ho.

Solution 7 - C#

This will do what you want but I don't know about performance:

var distinctValues =
    from cust in myCustomerList
    group cust by cust.CustomerId
    into gcust
    select gcust.First();

At least it's not verbose.

Solution 8 - C#

Something I have used which worked well for me.

/// <summary>
/// A class to wrap the IEqualityComparer interface into matching functions for simple implementation
/// </summary>
/// <typeparam name="T">The type of object to be compared</typeparam>
public class MyIEqualityComparer<T> : IEqualityComparer<T>
{
    /// <summary>
    /// Create a new comparer based on the given Equals and GetHashCode methods
    /// </summary>
    /// <param name="equals">The method to compute equals of two T instances</param>
    /// <param name="getHashCode">The method to compute a hashcode for a T instance</param>
    public MyIEqualityComparer(Func<T, T, bool> equals, Func<T, int> getHashCode)
    {
        if (equals == null)
            throw new ArgumentNullException("equals", "Equals parameter is required for all MyIEqualityComparer instances");
        EqualsMethod = equals;
        GetHashCodeMethod = getHashCode;
    }
    /// <summary>
    /// Gets the method used to compute equals
    /// </summary>
    public Func<T, T, bool> EqualsMethod { get; private set; }
    /// <summary>
    /// Gets the method used to compute a hash code
    /// </summary>
    public Func<T, int> GetHashCodeMethod { get; private set; }

    bool IEqualityComparer<T>.Equals(T x, T y)
    {
        return EqualsMethod(x, y);
    }

    int IEqualityComparer<T>.GetHashCode(T obj)
    {
        if (GetHashCodeMethod == null)
            return obj.GetHashCode();
        return GetHashCodeMethod(obj);
    }
}

Solution 9 - C#

All solutions I've seen here rely on selecting an already comparable field. If one needs to compare in a different way, though, this solution here seems to work generally, for something like:

somedoubles.Distinct(new LambdaComparer<double>((x, y) => Math.Abs(x - y) < double.Epsilon)).Count()

Solution 10 - C#

Take another way:

var distinctValues = myCustomerList.
Select(x => x._myCaustomerProperty).Distinct();

The sequence return distinct elements compare them by property '_myCaustomerProperty' .

Solution 11 - C#

You can use LambdaEqualityComparer:

var distinctValues
    = myCustomerList.Distinct(new LambdaEqualityComparer<OurType>((c1, c2) => c1.CustomerId == c2.CustomerId));


public class LambdaEqualityComparer<T> : IEqualityComparer<T>
	{
		public LambdaEqualityComparer(Func<T, T, bool> equalsFunction)
		{
			_equalsFunction = equalsFunction;
		}

		public bool Equals(T x, T y)
		{
			return _equalsFunction(x, y);
		}

		public int GetHashCode(T obj)
		{
			return obj.GetHashCode();
		}

		private readonly Func<T, T, bool> _equalsFunction;
	}

Solution 12 - C#

You can use InlineComparer

public class InlineComparer<T> : IEqualityComparer<T>
{
    //private readonly Func<T, T, bool> equalsMethod;
    //private readonly Func<T, int> getHashCodeMethod;
    public Func<T, T, bool> EqualsMethod { get; private set; }
    public Func<T, int> GetHashCodeMethod { get; private set; }

    public InlineComparer(Func<T, T, bool> equals, Func<T, int> hashCode)
    {
        if (equals == null) throw new ArgumentNullException("equals", "Equals parameter is required for all InlineComparer instances");
        EqualsMethod = equals;
        GetHashCodeMethod = hashCode;
    }

    public bool Equals(T x, T y)
    {
        return EqualsMethod(x, y);
    }

    public int GetHashCode(T obj)
    {
        if (GetHashCodeMethod == null) return obj.GetHashCode();
        return GetHashCodeMethod(obj);
    }
}

Usage sample:

  var comparer = new InlineComparer<DetalleLog>((i1, i2) => i1.PeticionEV == i2.PeticionEV && i1.Etiqueta == i2.Etiqueta, i => i.PeticionEV.GetHashCode() + i.Etiqueta.GetHashCode());
  var peticionesEV = listaLogs.Distinct(comparer).ToList();
  Assert.IsNotNull(peticionesEV);
  Assert.AreNotEqual(0, peticionesEV.Count);

Source: https://stackoverflow.com/a/5969691/206730
https://stackoverflow.com/questions/5969505/using-iequalitycomparer-for-union
https://stackoverflow.com/questions/188120/can-i-specify-my-explicit-type-comparator-inline

Solution 13 - C#

From .Net 6 (Preview 7) or later, there is a new build-in method Enumerable.DistinctBy to achieve this.

var distinctValues = myCustomerList.DistinctBy(c => c.CustomerId);

// With IEqualityComparer
var distinctValues = myCustomerList.DistinctBy(c => c.CustomerId, someEqualityComparer);

Solution 14 - C#

If Distinct() doesn't produce unique results, try this one:

var filteredWC = tblWorkCenter.GroupBy(cc => cc.WCID_I).Select(grp => grp.First()).Select(cc => new Model.WorkCenter { WCID = cc.WCID_I }).OrderBy(cc => cc.WCID); 

ObservableCollection<Model.WorkCenter> WorkCenter = new ObservableCollection<Model.WorkCenter>(filteredWC);

Solution 15 - C#

IEnumerable lambda extension:

public static class ListExtensions
{        
    public static IEnumerable<T> Distinct<T>(this IEnumerable<T> list, Func<T, int> hashCode)
    {
        Dictionary<int, T> hashCodeDic = new Dictionary<int, T>();

        list.ToList().ForEach(t => 
            {   
                var key = hashCode(t);
                if (!hashCodeDic.ContainsKey(key))
                    hashCodeDic.Add(key, t);
            });

        return hashCodeDic.Select(kvp => kvp.Value);
    }
}

Usage:

class Employee
{
    public string Name { get; set; }
    public int EmployeeID { get; set; }
}

//Add 5 employees to List
List<Employee> lst = new List<Employee>();

Employee e = new Employee { Name = "Shantanu", EmployeeID = 123456 };
lst.Add(e);
lst.Add(e);

Employee e1 = new Employee { Name = "Adam Warren", EmployeeID = 823456 };
lst.Add(e1);
//Add a space in the Name
Employee e2 = new Employee { Name = "Adam  Warren", EmployeeID = 823456 };
lst.Add(e2);
//Name is different case
Employee e3 = new Employee { Name = "adam warren", EmployeeID = 823456 };
lst.Add(e3);            

//Distinct (without IEqalityComparer<T>) - Returns 4 employees
var lstDistinct1 = lst.Distinct();

//Lambda Extension - Return 2 employees
var lstDistinct = lst.Distinct(employee => employee.EmployeeID.GetHashCode() ^ employee.Name.ToUpper().Replace(" ", "").GetHashCode()); 

Solution 16 - C#

A tricky way to do this is use Aggregate() extension, using a dictionary as accumulator with the key-property values as keys:

var customers = new List<Customer>();
        
var distincts = customers.Aggregate(new Dictionary<int, Customer>(), 
                                    (d, e) => { d[e.CustomerId] = e; return d; },
                                    d => d.Values);

And a GroupBy-style solution is using ToLookup():

var distincts = customers.ToLookup(c => c.CustomerId).Select(g => g.First());

Solution 17 - C#

I'm assuming you have an IEnumerable, and in your example delegate, you would like c1 and c2 to be referring to two elements in this list?

I believe you could achieve this with a self join var distinctResults = from c1 in myList join c2 in myList on

Solution 18 - C#

The Microsoft System.Interactive package has a version of Distinct that takes a key selector lambda. This is effectively the same as Jon Skeet's solution, but it may be helpful for people to know, and to check out the rest of the library.

Solution 19 - C#

Here's how you can do it:

public static class Extensions
{
	public static IEnumerable<T> MyDistinct<T, V>(this IEnumerable<T> query,
													Func<T, V> f, 
													Func<IGrouping<V,T>,T> h=null)
	{
		if (h==null) h=(x => x.First());
		return query.GroupBy(f).Select(h);
	}
}

This method allows you to use it by specifying one parameter like .MyDistinct(d => d.Name), but it also allows you to specify a having condition as a second parameter like so:

var myQuery = (from x in _myObject select x).MyDistinct(d => d.Name,
        x => x.FirstOrDefault(y=>y.Name.Contains("1") || y.Name.Contains("2"))
        );

N.B. This would also allow you to specify other functions like for example .LastOrDefault(...) as well.


If you want to expose just the condition, you can have it even simpler by implementing it as:

public static IEnumerable<T> MyDistinct2<T, V>(this IEnumerable<T> query,
												Func<T, V> f,
												Func<T,bool> h=null
												)
{
	if (h == null) h = (y => true);
	return query.GroupBy(f).Select(x=>x.FirstOrDefault(h));
}

In this case, the query would just look like:

var myQuery2 = (from x in _myObject select x).MyDistinct2(d => d.Name,
                    y => y.Name.Contains("1") || y.Name.Contains("2")
                    );

N.B. Here, the expression is simpler, but note .MyDistinct2 uses .FirstOrDefault(...) implicitly.


Note: The examples above are using the following demo class

class MyObject
{
	public string Name;
	public string Code;
}

private MyObject[] _myObject = {
	new MyObject() { Name = "Test1", Code = "T"},
	new MyObject() { Name = "Test2", Code = "Q"},
	new MyObject() { Name = "Test2", Code = "T"},
	new MyObject() { Name = "Test5", Code = "Q"}
};

Solution 20 - C#

I found this as the easiest solution.

public static IEnumerable<TSource> DistinctBy<TSource, TKey>
        (this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
    {
        return source.GroupBy(keySelector).Select(x => x.FirstOrDefault());
    }

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionTor HaugenView Question on Stackoverflow
Solution 1 - C#Carlo BosView Answer on Stackoverflow
Solution 2 - C#Jon SkeetView Answer on Stackoverflow
Solution 3 - C#Anestis KivranoglouView Answer on Stackoverflow
Solution 4 - C#Arasu RRKView Answer on Stackoverflow
Solution 5 - C#JaredParView Answer on Stackoverflow
Solution 6 - C#David KirklandView Answer on Stackoverflow
Solution 7 - C#Gordon FreemanView Answer on Stackoverflow
Solution 8 - C#KleinuxView Answer on Stackoverflow
Solution 9 - C#Dmitry LedentsovView Answer on Stackoverflow
Solution 10 - C#BobView Answer on Stackoverflow
Solution 11 - C#Валентин МироновView Answer on Stackoverflow
Solution 12 - C#KiquenetView Answer on Stackoverflow
Solution 13 - C#Vijay NirmalView Answer on Stackoverflow
Solution 14 - C#Andy SinghView Answer on Stackoverflow
Solution 15 - C#ShantanuView Answer on Stackoverflow
Solution 16 - C#Arturo MenchacaView Answer on Stackoverflow
Solution 17 - C#MattHView Answer on Stackoverflow
Solution 18 - C#Niall ConnaughtonView Answer on Stackoverflow
Solution 19 - C#MattView Answer on Stackoverflow
Solution 20 - C#Tulshi DasView Answer on Stackoverflow