C# 为什么没有 Linq 方法通过谓词返回不同的值?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/520030/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Why is there no Linq method to return distinct values by a predicate?
提问by Keith
I want to get the distinct values in a list, but not by the standard equality comparison.
我想在列表中获取不同的值,但不是通过标准的相等比较。
What I want to do is something like this:
我想做的是这样的:
return myList.Distinct( (x, y) => x.Url == y.Url );
I can't, there's no extension method in Linq that will do this - just one that takes an IEqualityComparer
.
我不能,Linq 中没有扩展方法可以做到这一点 - 只有一个需要IEqualityComparer
.
I can hack around it with this:
我可以用这个来破解它:
return myList.GroupBy( x => x.Url ).Select( g => g.First() );
But that seems messy. It also doesn't quite do the same thing - I can only use it here because I have a single key.
但这似乎很混乱。它也不完全相同 - 我只能在这里使用它,因为我只有一个键。
I could also add my own:
我也可以添加我自己的:
public static IEnumerable<T> Distinct<T>(
this IEnumerable<T> input, Func<T,T,bool> compare )
{
//write my own here
}
But that does seem rather like writing something that should be there in the first place.
但这确实看起来更像是在写一些应该首先存在的东西。
Anyone know why this method isn't there?
有人知道为什么没有这种方法吗?
Am I missing something?
我错过了什么吗?
采纳答案by Jon Skeet
It's annoying, certainly. It's also part of my "MoreLINQ" project which I must pay some attention to at some point :) There are plenty of other operations which make sense when acting on a projection, but returning the original - MaxBy and MinBy spring to mind.
这很烦人,当然。它也是我的“MoreLINQ”项目的一部分,我必须在某个时候注意它:) 还有很多其他操作在对投影进行操作时是有意义的,但是回到原来的 - MaxBy 和 MinBy 浮现在脑海中。
As you say, it's easy to write - although I prefer the name "DistinctBy" to match OrderBy etc. Here's my implementation if you're interested:
正如您所说,它很容易编写 - 尽管我更喜欢名称“DistinctBy”来匹配 OrderBy 等。如果您有兴趣,这是我的实现:
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;
}
}
}
回答by Amy B
But that seems messy.
但这似乎很混乱。
It's not messy, it's correct.
不乱,是对的。
- If you want
Distinct
Programmers by FirstName and there are four Amy's, which one do you want? - If you
Group
programmers By FirstName and take theFirst
one, then it is clear what you want to do in the case of four Amy's.
- 如果您想要
Distinct
FirstName 的 Programmers 并且有四个 Amy,您想要哪个? - 如果你的
Group
程序员 By FirstName 选择First
一个,那么在四个 Amy 的情况下你想做什么就很清楚了。
I can only use it here because I have a single key.
我只能在这里使用它,因为我只有一把钥匙。
You can do a multiple key "distinct" with the same pattern:
您可以使用相同的模式执行多个键“不同”:
return myList
.GroupBy( x => new { x.Url, x.Age } )
.Select( g => g.First() );
回答by SVC
Jon, your solution is pretty good. One minor change though. I don't think we need EqualityComparer.Default in there. Here is my solution (ofcourse the starting point was Jon Skeet's solution)
乔恩,你的解决方案非常好。不过有一点小改动。我认为我们不需要 EqualityComparer.Default 。这是我的解决方案(当然起点是 Jon Skeet 的解决方案)
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;
}
}
}
回答by Cerbrus
Using AmyB's answer, I've written a small DistinctBy
extension method, to allow a predicate to be passed:
使用 AmyB 的回答,我编写了一个小的DistinctBy
扩展方法,以允许传递谓词:
/// <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<TSource>.</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());
}
You can now pass a predicate to group the list by:
您现在可以传递一个谓词来对列表进行分组:
var distinct = myList.DistinctBy(x => x.Id);
Or group by multiple properties:
或按多个属性分组:
var distinct = myList.DistinctBy(x => new { x.Id, x.Title });