Какой ваш любимый оператор LINQ to Objects, который не встроен?
С методов расширения, мы можем написать удобные операторы LINQ, которые решают общие проблемы.
Я хочу услышать, какие методы или перегрузки вам не хватает в System.Linq
пространство имен и как вы реализовали их.
предпочтительны чистые и элегантные реализации, возможно, с использованием существующих методов.
30 ответов:
Добавить & Вставить
/// <summary>Adds a single element to the end of an IEnumerable.</summary> /// <typeparam name="T">Type of enumerable to return.</typeparam> /// <returns>IEnumerable containing all the input elements, followed by the /// specified additional element.</returns> public static IEnumerable<T> Append<T>(this IEnumerable<T> source, T element) { if (source == null) throw new ArgumentNullException("source"); return concatIterator(element, source, false); } /// <summary>Adds a single element to the start of an IEnumerable.</summary> /// <typeparam name="T">Type of enumerable to return.</typeparam> /// <returns>IEnumerable containing the specified additional element, followed by /// all the input elements.</returns> public static IEnumerable<T> Prepend<T>(this IEnumerable<T> tail, T head) { if (tail == null) throw new ArgumentNullException("tail"); return concatIterator(head, tail, true); } private static IEnumerable<T> concatIterator<T>(T extraElement, IEnumerable<T> source, bool insertAtStart) { if (insertAtStart) yield return extraElement; foreach (var e in source) yield return e; if (!insertAtStart) yield return extraElement; }
Я удивлен, что никто не упомянул MoreLINQ project еще. Это было начато Джон Скит и получил некоторые разработчики на этом пути. Со страницы проекта:
LINQ to Objects отсутствует несколько желательные особенности.
этот проект повысит LINQ до Объекты с дополнительными методами, в манера, которая соответствует духу Линк.
посмотри операторы Обзор страница wiki для списка реализованных операторов.
Это, безусловно, хороший способ узнать из какого-то чистого и элегантного исходного кода.
каждого
ничего для пуристов, но черт возьми это полезно!
public static void Each<T>(this IEnumerable<T> items, Action<T> action) { foreach (var i in items) action(i); }
ToQueue & ToStack
/// <summary>Creates a <see cref="Queue<T>"/> from an enumerable /// collection.</summary> public static Queue<T> ToQueue<T>(this IEnumerable<T> source) { if (source == null) throw new ArgumentNullException("source"); return new Queue<T>(source); } /// <summary>Creates a <see cref="Stack<T>"/> from an enumerable /// collection.</summary> public static Stack<T> ToStack<T>(this IEnumerable<T> source) { if (source == null) throw new ArgumentNullException("source"); return new Stack<T>(source); }
и не
C# эквиваленты двух других известных конструкций SQL
/// <summary> /// Determines if the source value is contained in the list of possible values. /// </summary> /// <typeparam name="T">The type of the objects</typeparam> /// <param name="value">The source value</param> /// <param name="values">The list of possible values</param> /// <returns> /// <c>true</c> if the source value matches at least one of the possible values; otherwise, <c>false</c>. /// </returns> public static bool In<T>(this T value, params T[] values) { if (values == null) return false; if (values.Contains<T>(value)) return true; return false; } /// <summary> /// Determines if the source value is contained in the list of possible values. /// </summary> /// <typeparam name="T">The type of the objects</typeparam> /// <param name="value">The source value</param> /// <param name="values">The list of possible values</param> /// <returns> /// <c>true</c> if the source value matches at least one of the possible values; otherwise, <c>false</c>. /// </returns> public static bool In<T>(this T value, IEnumerable<T> values) { if (values == null) return false; if (values.Contains<T>(value)) return true; return false; } /// <summary> /// Determines if the source value is not contained in the list of possible values. /// </summary> /// <typeparam name="T">The type of the objects</typeparam> /// <param name="value">The source value</param> /// <param name="values">The list of possible values</param> /// <returns> /// <c>false</c> if the source value matches at least one of the possible values; otherwise, <c>true</c>. /// </returns> public static bool NotIn<T>(this T value, params T[] values) { return In(value, values) == false; } /// <summary> /// Determines if the source value is not contained in the list of possible values. /// </summary> /// <typeparam name="T">The type of the objects</typeparam> /// <param name="value">The source value</param> /// <param name="values">The list of possible values</param> /// <returns> /// <c>false</c> if the source value matches at least one of the possible values; otherwise, <c>true</c>. /// </returns> public static bool NotIn<T>(this T value, IEnumerable<T> values) { return In(value, values) == false; }
AsIEnumerable
/// <summary> /// Returns a sequence containing one element. /// </summary> public static IEnumerable<T> AsIEnumerable<T>(this T obj) { yield return obj; }
использование:
var nums = new[] {12, 20, 6}; var numsWith5Prepended = 5.AsIEnumerable().Concat(nums);
JoinString
в основном то же самое, что
string.Join
, но:
с возможностью использовать его в любой коллекции, а не только в коллекции строк (вызовы
ToString
на каждый элемент)с возможностью добавления префикса и суффикса к каждой строке.
как метод расширения. Я нахожу
string.Join
раздражает, потому что он статичен, что означает, что в цепочке операций он лексически не в правильном порядок.
/// <summary> /// Turns all elements in the enumerable to strings and joins them using the /// specified string as the separator and the specified prefix and suffix for /// each string. /// <example> /// <code> /// var a = (new[] { "Paris", "London", "Tokyo" }).JoinString(", ", "[", "]"); /// // a contains "[Paris], [London], [Tokyo]" /// </code> /// </example> /// </summary> public static string JoinString<T>(this IEnumerable<T> values, string separator = null, string prefix = null, string suffix = null) { if (values == null) throw new ArgumentNullException("values"); using (var enumerator = values.GetEnumerator()) { if (!enumerator.MoveNext()) return ""; StringBuilder sb = new StringBuilder(); sb.Append(prefix).Append(enumerator.Current.ToString()).Append(suffix); while (enumerator.MoveNext()) sb.Append(separator).Append(prefix) .Append(enumerator.Current.ToString()).Append(suffix); return sb.ToString(); } }
ордер
/// <summary>Sorts the elements of a sequence in ascending order.</summary> public static IEnumerable<T> Order<T>(this IEnumerable<T> source) { return source.OrderBy(x => x); }
перетасовка
public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> items) { var random = new Random(); return items.OrderBy(x => random.Next()); }
EDIT: кажется, есть несколько проблем с вышеуказанной реализацией. Вот улучшенная версия на основе кода @LukeH и комментариев от @ck и @Strilanc.
private static Random _rand = new Random(); public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source) { var items = source == null ? new T[] { } : source.ToArray(); var count = items.Length; while(count > 0) { int toReturn = _rand.Next(0, count); yield return items[toReturn]; items[toReturn] = items[count - 1]; count--; } }
цикл
вот это круто, я просто подумал. (Если Я просто думал об этом, может это не так полезно? Но я подумал об этом, потому что мне это нужно.) Цикл через последовательность несколько раз, чтобы создать бесконечную последовательность. Это выполняет что-то вроде того, что
Enumerable.Range
иEnumerable.Repeat
дать вам, за исключением того, что он может быть использован для произвольные (в отличие отRange
)последовательность (в отличие отRepeat
):public static IEnumerable<T> Loop<T>(this IEnumerable<T> source) { while (true) { foreach (T item in source) { yield return item; } } }
использование:
var numbers = new[] { 1, 2, 3 }; var looped = numbers.Loop(); foreach (int x in looped.Take(10)) { Console.WriteLine(x); }
выход:
1 2 3 1 2 3 1 2 3 1примечание: Я полагаю, вы могли бы также выполнить это с чем-то вроде:
var looped = Enumerable.Repeat(numbers, int.MaxValue).SelectMany(seq => seq);
...но я думаю
Loop
понятнее.
MinElement
Min
возвращает только минимальное значение, возвращаемое указанным выражением, но не исходный элемент, который дал этот минимальный элемент./// <summary>Returns the first element from the input sequence for which the /// value selector returns the smallest value.</summary> public static T MinElement<T, TValue>(this IEnumerable<T> source, Func<T, TValue> valueSelector) where TValue : IComparable<TValue> { if (source == null) throw new ArgumentNullException("source"); if (valueSelector == null) throw new ArgumentNullException("valueSelector"); using (var enumerator = source.GetEnumerator()) { if (!enumerator.MoveNext()) throw new InvalidOperationException("source contains no elements."); T minElem = enumerator.Current; TValue minValue = valueSelector(minElem); while (enumerator.MoveNext()) { TValue value = valueSelector(enumerator.Current); if (value.CompareTo(minValue) < 0) { minValue = value; minElem = enumerator.Current; } } return minElem; } }
IndexOf
/// <summary> /// Returns the index of the first element in this <paramref name="source"/> /// satisfying the specified <paramref name="condition"/>. If no such elements /// are found, returns -1. /// </summary> public static int IndexOf<T>(this IEnumerable<T> source, Func<T, bool> condition) { if (source == null) throw new ArgumentNullException("source"); if (condition == null) throw new ArgumentNullException("condition"); int index = 0; foreach (var v in source) { if (condition(v)) return index; index++; } return -1; }
блоки
возвращает куски определенного размера.
x.Chunks(2)
of1,2,3,4,5
возвращает два массива с1,2
и3,4
.x.Chunks(2,true)
вернутся1,2
,3,4
и5
.public static IEnumerable<T[]> Chunks<T>(this IEnumerable<T> xs, int size, bool returnRest = false) { var curr = new T[size]; int i = 0; foreach (var x in xs) { if (i == size) { yield return curr; i = 0; curr = new T[size]; } curr[i++] = x; } if (returnRest) yield return curr.Take(i).ToArray(); }
ToHashSet
public static HashSet<T> ToHashSet<T>(this IEnumerable<T> items) { return new HashSet<T>(items); }
FirstOrDefault со значением по умолчанию указано
/// <summary> /// Returns the first element of a sequence, or a default value if the /// sequence contains no elements. /// </summary> /// <typeparam name="T">The type of the elements of /// <paramref name="source"/>.</typeparam> /// <param name="source">The <see cref="IEnumerable<T>"/> to return /// the first element of.</param> /// <param name="default">The default value to return if the sequence contains /// no elements.</param> /// <returns><paramref name="default"/> if <paramref name="source"/> is empty; /// otherwise, the first element in <paramref name="source"/>.</returns> public static T FirstOrDefault<T>(this IEnumerable<T> source, T @default) { if (source == null) throw new ArgumentNullException("source"); using (var e = source.GetEnumerator()) { if (!e.MoveNext()) return @default; return e.Current; } } /// <summary> /// Returns the first element of a sequence, or a default value if the sequence /// contains no elements. /// </summary> /// <typeparam name="T">The type of the elements of /// <paramref name="source"/>.</typeparam> /// <param name="source">The <see cref="IEnumerable<T>"/> to return /// the first element of.</param> /// <param name="predicate">A function to test each element for a /// condition.</param> /// <param name="default">The default value to return if the sequence contains /// no elements.</param> /// <returns><paramref name="default"/> if <paramref name="source"/> is empty /// or if no element passes the test specified by <paramref name="predicate"/>; /// otherwise, the first element in <paramref name="source"/> that passes /// the test specified by <paramref name="predicate"/>.</returns> public static T FirstOrDefault<T>(this IEnumerable<T> source, Func<T, bool> predicate, T @default) { if (source == null) throw new ArgumentNullException("source"); if (predicate == null) throw new ArgumentNullException("predicate"); using (var e = source.GetEnumerator()) { while (true) { if (!e.MoveNext()) return @default; if (predicate(e.Current)) return e.Current; } } }
InsertBetween
вставляет элемент между каждой парой последовательных элементов.
/// <summary>Inserts the specified item in between each element in the input /// collection.</summary> /// <param name="source">The input collection.</param> /// <param name="extraElement">The element to insert between each consecutive /// pair of elements in the input collection.</param> /// <returns>A collection containing the original collection with the extra /// element inserted. For example, new[] { 1, 2, 3 }.InsertBetween(0) returns /// { 1, 0, 2, 0, 3 }.</returns> public static IEnumerable<T> InsertBetween<T>( this IEnumerable<T> source, T extraElement) { return source.SelectMany(val => new[] { extraElement, val }).Skip(1); }
EmptyIfNull
Это спорный вопрос; я уверен, что многие пуристы будут возражать против "метода экземпляра" на
null
успех./// <summary> /// Returns an IEnumerable<T> as is, or an empty IEnumerable<T> if it is null /// </summary> public static IEnumerable<T> EmptyIfNull<T>(this IEnumerable<T> source) { return source ?? Enumerable.Empty<T>(); }
использование:
foreach(var item in myEnumerable.EmptyIfNull()) { Console.WriteLine(item); }
парсить
этот включает в себя пользовательский делегат (мог бы использовать интерфейс, но я пошел с делегатом, как это было проще), который используется для анализа последовательности строк в последовательность значений, пропуская элементы, где разбор не.
public delegate bool TryParser<T>(string text, out T value); public static IEnumerable<T> Parse<T>(this IEnumerable<string> source, TryParser<T> parser) { source.ThrowIfNull("source"); parser.ThrowIfNull("parser"); foreach (string str in source) { T value; if (parser(str, out value)) { yield return value; } } }
использование:
var strings = new[] { "1", "2", "H3llo", "4", "five", "6", "se7en" }; var numbers = strings.Parse<int>(int.TryParse); foreach (int x in numbers) { Console.WriteLine(x); }
выход:
1 2 4 6именование сложно для этого. Я не уверен, что
Parse
это лучший вариант (это и простой, по крайней мере), или если что-то вродеParseWhereValid
будет лучше.
ZipMerge
Это моя версия
Zip
, которая работает как настоящая молния. Он не проецирует два значения в одно, но возвращает объединенный IEnumerable. Возможны перегрузки, пропуск правого и / или левого хвоста.public static IEnumerable<TSource> ZipMerge<TSource>( this IEnumerable<TSource> first, IEnumerable<TSource> second) { using (var secondEnumerator = second.GetEnumerator()) { foreach (var item in first) { yield return item; if (secondEnumerator.MoveNext()) yield return secondEnumerator.Current; } while (secondEnumerator.MoveNext()) yield return secondEnumerator.Current; } }
RandomSample
вот простая функция, которая полезна, если у вас есть средний-большой набор данных (скажем, более 100 наименований) и вы хотите глазное яблоко просто случайная выборка из него.
public static IEnumerable<T> RandomSample<T>(this IEnumerable<T> source, double percentage) { source.ThrowIfNull("source"); var r = new Random(); return source.Where(x => (r.NextDouble() * 100.0) < percentage); }
использование:
List<DataPoint> data = GetData(); // Sample roughly 3% of the data var sample = data.RandomSample(3.0); // Verify results were correct for this sample foreach (DataPoint point in sample) { Console.WriteLine("{0} => {1}", point, DoCalculation(point)); }
Примечания:
- не очень подходит для крошечных коллекций, поскольку количество возвращаемых элементов является вероятностным (может легко вернуть ноль в небольшой последовательности).
- не очень подходит для огромных коллекций или запросы к базе данных, как это включает в себя перечисление по каждому элементу в последовательности.
AssertCount
эффективно определяет, если an
IEnumerable<T>
содержит по крайней мере / точно / не более определенного количества элементов.public enum CountAssertion { AtLeast, Exact, AtMost } /// <summary> /// Asserts that the number of items in a sequence matching a specified predicate satisfies a specified CountAssertion. /// </summary> public static bool AssertCount<T>(this IEnumerable<T> source, int countToAssert, CountAssertion assertion, Func<T, bool> predicate) { if (source == null) throw new ArgumentNullException("source"); if (predicate == null) throw new ArgumentNullException("predicate"); return source.Where(predicate).AssertCount(countToAssert, assertion); } /// <summary> /// Asserts that the number of elements in a sequence satisfies a specified CountAssertion. /// </summary> public static bool AssertCount<T>(this IEnumerable<T> source, int countToAssert, CountAssertion assertion) { if (source == null) throw new ArgumentNullException("source"); if (countToAssert < 0) throw new ArgumentOutOfRangeException("countToAssert"); switch (assertion) { case CountAssertion.AtLeast: return AssertCountAtLeast(source, GetFastCount(source), countToAssert); case CountAssertion.Exact: return AssertCountExact(source, GetFastCount(source), countToAssert); case CountAssertion.AtMost: return AssertCountAtMost(source, GetFastCount(source), countToAssert); default: throw new ArgumentException("Unknown CountAssertion.", "assertion"); } } private static int? GetFastCount<T>(IEnumerable<T> source) { var genericCollection = source as ICollection<T>; if (genericCollection != null) return genericCollection.Count; var collection = source as ICollection; if (collection != null) return collection.Count; return null; } private static bool AssertCountAtMost<T>(IEnumerable<T> source, int? fastCount, int countToAssert) { if (fastCount.HasValue) return fastCount.Value <= countToAssert; int countSoFar = 0; foreach (var item in source) { if (++countSoFar > countToAssert) return false; } return true; } private static bool AssertCountExact<T>(IEnumerable<T> source, int? fastCount, int countToAssert) { if (fastCount.HasValue) return fastCount.Value == countToAssert; int countSoFar = 0; foreach (var item in source) { if (++countSoFar > countToAssert) return false; } return countSoFar == countToAssert; } private static bool AssertCountAtLeast<T>(IEnumerable<T> source, int? fastCount, int countToAssert) { if (countToAssert == 0) return true; if (fastCount.HasValue) return fastCount.Value >= countToAssert; int countSoFar = 0; foreach (var item in source) { if (++countSoFar >= countToAssert) return true; } return false; }
использование:
var nums = new[] { 45, -4, 35, -12, 46, -98, 11 }; bool hasAtLeast3Positive = nums.AssertCount(3, CountAssertion.AtLeast, i => i > 0); //true bool hasAtMost1Negative = nums.AssertCount(1, CountAssertion.AtMost, i => i < 0); //false bool hasExactly2Negative = nums.AssertCount(2, CountAssertion.Exact, i => i < 0); //false
окно
перечисляет массивы ("окна") с длиной
size
содержит самые последние значения.{ 0, 1, 2, 3 }
превращается в{ [0, 1], [1, 2], [2, 3] }
.Я использую это, например, чтобы нарисовать линейный график, соединяя две точки.
public static IEnumerable<TSource[]> Window<TSource>( this IEnumerable<TSource> source) { return source.Window(2); } public static IEnumerable<TSource[]> Window<TSource>( this IEnumerable<TSource> source, int size) { if (size <= 0) throw new ArgumentOutOfRangeException("size"); return source.Skip(size).WindowHelper(size, source.Take(size)); } private static IEnumerable<TSource[]> WindowHelper<TSource>( this IEnumerable<TSource> source, int size, IEnumerable<TSource> init) { Queue<TSource> q = new Queue<TSource>(init); yield return q.ToArray(); foreach (var value in source) { q.Dequeue(); q.Enqueue(value); yield return q.ToArray(); } }
Один, Два, MoreThanOne, По Крайней Мере, AnyAtAll
public static bool One<T>(this IEnumerable<T> enumerable) { using (var enumerator = enumerable.GetEnumerator()) return enumerator.MoveNext() && !enumerator.MoveNext(); } public static bool Two<T>(this IEnumerable<T> enumerable) { using (var enumerator = enumerable.GetEnumerator()) return enumerator.MoveNext() && enumerator.MoveNext() && !enumerator.MoveNext(); } public static bool MoreThanOne<T>(this IEnumerable<T> enumerable) { return enumerable.Skip(1).Any(); } public static bool AtLeast<T>(this IEnumerable<T> enumerable, int count) { using (var enumerator = enumerable.GetEnumerator()) for (var i = 0; i < count; i++) if (!enumerator.MoveNext()) return false; return true; } public static bool AnyAtAll<T>(this IEnumerable<T> enumerable) { return enumerable != null && enumerable.Any(); }
SkipLast & TakeLast
/// <summary> /// Enumerates the items of this collection, skipping the last /// <paramref name="count"/> items. Note that the memory usage of this method /// is proportional to <paramref name="count"/>, but the source collection is /// only enumerated once, and in a lazy fashion. Also, enumerating the first /// item will take longer than enumerating subsequent items. /// </summary> public static IEnumerable<T> SkipLast<T>(this IEnumerable<T> source, int count) { if (source == null) throw new ArgumentNullException("source"); if (count < 0) throw new ArgumentOutOfRangeException("count", "count cannot be negative."); if (count == 0) return source; return skipLastIterator(source, count); } private static IEnumerable<T> skipLastIterator<T>(IEnumerable<T> source, int count) { var queue = new T[count]; int headtail = 0; // tail while we're still collecting, both head & tail // afterwards because the queue becomes completely full int collected = 0; foreach (var item in source) { if (collected < count) { queue[headtail] = item; headtail++; collected++; } else { if (headtail == count) headtail = 0; yield return queue[headtail]; queue[headtail] = item; headtail++; } } } /// <summary> /// Returns a collection containing only the last <paramref name="count"/> /// items of the input collection. This method enumerates the entire /// collection to the end once before returning. Note also that the memory /// usage of this method is proportional to <paramref name="count"/>. /// </summary> public static IEnumerable<T> TakeLast<T>(this IEnumerable<T> source, int count) { if (source == null) throw new ArgumentNullException("source"); if (count < 0) throw new ArgumentOutOfRangeException("count", "count cannot be negative."); if (count == 0) return new T[0]; var queue = new Queue<T>(count + 1); foreach (var item in source) { if (queue.Count == count) queue.Dequeue(); queue.Enqueue(item); } return queue.AsEnumerable(); }
дубликаты
используется в сочетании с методом как Ани
AssertCount
метод (я использую один называетсяCountAtLeast
), становится очень легко найти элементы в последовательности, которые появляются несколько раз:public static IEnumerable<T> Duplicates<T, TKey>(this IEnumerable<T> source, Func<T, TKey> keySelector = null, IEqualityComparer<TKey> comparer = null) { source.ThrowIfNull("source"); keySelector = keySelector ?? new Func<T, TKey>(x => x); comparer = comparer ?? EqualityComparer<TKey>.Default; return source.GroupBy(keySelector, comparer) .Where(g => g.CountAtLeast(2)) .SelectMany(g => g); }
WhereIf
дополнительно
Where
положения оIEnumerable
иIQueryable
. Избегает операторов if при построении предикатов и лямбд для запроса. Полезно, когда вы не знаете, во время компиляции, должен ли применяться фильтр.public static IEnumerable<TSource> WhereIf<TSource>( this IEnumerable<TSource> source, bool condition, Func<TSource, bool> predicate) { return condition ? source.Where(predicate) : source; }
использование:
var custs = Customers.WhereIf(someBool, x=>x.EyeColor=="Green");
LINQ WhereIf At ExtensionMethod.NET и заимствовано у блог Андрея.
ToList и ToDictionary с начальной емкостью
перегрузки ToList и ToDictionary, которые предоставляют начальную емкость базовых классов коллекции. Иногда полезно, когда длина источника известна или ограничена.
public static List<TSource> ToList<TSource>( this IEnumerable<TSource> source, int capacity) { if (source == null) { throw new ArgumentNullException("source"); } var list = new List<TSource>(capacity); list.AddRange(source); return list; } public static Dictionary<TKey, TSource> ToDictionary<TSource, TKey>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, int capacity, IEqualityComparer<TKey> comparer = null) { return source.ToDictionary<TSource, TKey, TSource>( keySelector, x => x, capacity, comparer); } public static Dictionary<TKey, TElement> ToDictionary<TSource, TKey, TElement>( this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector, int capacity, IEqualityComparer<TKey> comparer = null) { if (source == null) { throw new ArgumentNullException("source"); } if (keySelector == null) { throw new ArgumentNullException("keySelector"); } if (elementSelector == null) { throw new ArgumentNullException("elementSelector"); } var dictionary = new Dictionary<TKey, TElement>(capacity, comparer); foreach (TSource local in source) { dictionary.Add(keySelector(local), elementSelector(local)); } return dictionary; }
CountUpTo
static int CountUpTo<T>(this IEnumerable<T> source, int maxCount) { if (maxCount == 0) return 0; var genericCollection = source as ICollection<T>; if (genericCollection != null) return Math.Min(maxCount, genericCollection.Count); var collection = source as ICollection; if (collection != null) return Math.Min(maxCount, collection.Count); int count = 0; foreach (T item in source) if (++count >= maxCount) break; return count; }