Все упомянутые решения до сих пор выполняют GroupBy. Даже если мне нужен только первый дубликат, все элементы коллекций перечисляются хотя бы один раз.
Следующая функция расширения прекращает перечисление, как только дубликат найден. Это продолжается, если запрашивается следующий дубликат.
Как всегда в LINQ, есть две версии, одна с IEqualityComparer, а другая без него.
public static IEnumerable<TSource> ExtractDuplicates(this IEnumerable<TSource> source)
{
return source.ExtractDuplicates(null);
}
public static IEnumerable<TSource> ExtractDuplicates(this IEnumerable<TSource source,
IEqualityComparer<TSource> comparer);
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (comparer == null)
comparer = EqualityCompare<TSource>.Default;
HashSet<TSource> foundElements = new HashSet<TSource>(comparer);
foreach (TSource sourceItem in source)
{
if (!foundElements.Contains(sourceItem))
{ // we've not seen this sourceItem before. Add to the foundElements
foundElements.Add(sourceItem);
}
else
{ // we've seen this item before. It is a duplicate!
yield return sourceItem;
}
}
}
Использование:
IEnumerable<MyClass> myObjects = ...
// check if has duplicates:
bool hasDuplicates = myObjects.ExtractDuplicates().Any();
// or find the first three duplicates:
IEnumerable<MyClass> first3Duplicates = myObjects.ExtractDuplicates().Take(3)
// or find the first 5 duplicates that have a Name = "MyName"
IEnumerable<MyClass> myNameDuplicates = myObjects.ExtractDuplicates()
.Where(duplicate => duplicate.Name == "MyName")
.Take(5);
Для всех этих операторов linq коллекция анализируется только до тех пор, пока запрошенные элементы не будут найдены. Остальная часть последовательности не интерпретируется.
ИМХО, это повышение эффективности, чтобы рассмотреть.