Why is there no Linq method for returning individual values โ€‹โ€‹by a predicate?

I want to get individual values โ€‹โ€‹in a list, but not a standard equality comparison.

I want to do something like this:

return myList.Distinct( (x, y) => x.Url == y.Url ); 

I canโ€™t, there will be no extension method in Linq that will do this - only one that accepts IEqualityComparer .

I can crack it with this:

 return myList.GroupBy( x => x.Url ).Select( g => g.First() ); 

But that seems messy. It is also not quite the same - I can only use it here because I have one key.

I can also add my own:

 public static IEnumerable<T> Distinct<T>( this IEnumerable<T> input, Func<T,T,bool> compare ) { //write my own here } 

But rather, it looks like it should be there in the first place.

Does anyone know why this method is not?

Did I miss something?

+49
c # linq distinct
Feb 06 '09 at 11:51
source share
4 answers

This is annoying, of course. This is also part of my MoreLINQ project, which I should pay attention to for some time :) There are many other operations that make sense when they act on the projection, but return the original - MaxBy and MinBy spring.

As you say, it's easy to write โ€” although I prefer the name โ€œDistinctByโ€ to match OrderBy, etc. Here is my implementation if you are interested:

  public static IEnumerable<TSource> DistinctBy<TSource, TKey> (this IEnumerable<TSource> source, Func<TSource, TKey> keySelector) { return source.DistinctBy(keySelector, EqualityComparer<TKey>.Default); } public static IEnumerable<TSource> DistinctBy<TSource, TKey> (this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey> comparer) { if (source == null) { throw new ArgumentNullException("source"); } if (keySelector == null) { throw new ArgumentNullException("keySelector"); } if (comparer == null) { throw new ArgumentNullException("comparer"); } 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; } } } 
+53
Feb 06 '09 at 12:00
source share

But it seems dirty.

Itโ€™s not dirty, itโ€™s right.

  • If you need Distinct programmers from FirstName and there are four Amy, which one do you need?
  • If you are Group Programmers By FirstName and chose First , then it is clear what you want to do with the four Amy.

I can only use it here because I have one key.

You can make multiple keys "excellent" with the same pattern:

 return myList .GroupBy( x => new { x.Url, x.Age } ) .Select( g => g.First() ); 
+35
Feb 06 '09 at 13:38
source share

John, your decision is pretty good. However, one minor change. I do not think we need EqualityComparer.Default. Here is my solution (of course, John Skeet's decision was the starting point)

  public static IEnumerable<T> DistinctBy<T, TKey>(this IEnumerable<T> source, Func<T, TKey> keySelector) { //TODO All arg checks HashSet<TKey> keys = new HashSet<TKey>(); foreach (T item in source) { TKey key = keySelector(item); if (!keys.Contains(key)) { keys.Add(key); yield return item; } } } 
+3
Oct 08 2018-10-10
source share

Using AmyB's answer , I wrote a small DistinctBy extension method that allows you to pass a predicate:

 /// <summary> /// Distinct method that accepts a perdicate /// </summary> /// <typeparam name="TSource">The type of the t source.</typeparam> /// <typeparam name="TKey">The type of the t key.</typeparam> /// <param name="source">The source.</param> /// <param name="predicate">The predicate.</param> /// <returns>IEnumerable&lt;TSource&gt;.</returns> /// <exception cref="System.ArgumentNullException">source</exception> public static IEnumerable<TSource> DistinctBy<TSource, TKey> (this IEnumerable<TSource> source, Func<TSource, TKey> predicate) { if (source == null) throw new ArgumentNullException("source"); return source .GroupBy(predicate) .Select(x => x.First()); } 

Now you can pass the predicate to group the list:

 var distinct = myList.DistinctBy(x => x.Id); 

Or group by several properties:

 var distinct = myList.DistinctBy(x => new { x.Id, x.Title }); 
+2
Sep 01 '16 at 8:02
source share



All Articles