你最喜欢的LINQ to Objects运算符是不是内置的?

使用扩展方法,我们可以编写方便的LINQ操作符来解决一般问题。

我想知道在System.Linq命名空间中缺less哪些方法或重载以及如何实现它们。

清洁和优雅的实现,可能使用现有的方法,是首选。

追加&附加

 /// <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项目呢。 这是由Jon Skeet创办的,并且在这个过程中获得了一些开发者。 从项目页面:

LINQ to Objects缺less一些理想的function。

这个项目将以一种保持LINQ精神的方式,通过额外的方法来增强LINQ to Objects。

查看运营商概述 wiki页面,查看已实施的运营商列表。

从一些干净优雅的源代码中学习是一个很好的方法。

纯粹主义者什么都没有,但它是有用的!

  public static void Each<T>(this IEnumerable<T> items, Action<T> action) { foreach (var i in items) action(i); } 

去追求

 /// <summary>Creates a <see cref="Queue&lt;T&gt;"/> 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&lt;T&gt;"/> 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); } 

是空的

 public static bool IsEmpty<T>(this IEnumerable<T> source) { return !source.Any(); } 

In和NotIn

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 ,但是:

  • 能够在任何集合上使用它,而不仅仅是一个string集合(在每个元素上调用ToString

  • 能够为每个string添加前缀和后缀。

  • 作为扩展方法。 我发现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()); } 

编辑:这似乎有几个问题与上述实施。 这是一个基于@ 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.RangeEnumerable.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只返回指定expression式返回的最小值,而不返回给出该最小元素的原始元素。

 /// <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; } } 

指数

 /// <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) . 1,2,3,4,5 x.Chunks(2)将返回1,23,4两个数组。 x.Chunks(2,true)将返回3,45

 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&lt;T&gt;"/> 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&lt;T&gt;"/> 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); } 

parsing

这个涉及到一个自定义的委托(可以使用一个IParser<T>接口,但是我使用了一个委托,因为它更简单),用于将一系列stringparsing为一个值序列,跳过元素parsing失败。

 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是否是最好的select(至less这简单),或者像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; } } 

随机抽样

这里有一个简单的函数,如果你有一个中等大小的数据集(比如说超过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)); } 

笔记:

  1. 由于返回的项目数是概率性的(可以很容易地在一个小序列上归零),所以对于微小的集合来说并不合适。
  2. 对于庞大的集合或数据库查询来说并不合适,因为它涉及列举序列中的每个项目。

AssertCount

有效地确定IEnumerable<T>至less包含/完全/至多包含一定数量的元素。

 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 

Window

Enumerates arrays ("windows") with the length of size containing the most current values.
{ 0, 1, 2, 3 } becomes to { [0, 1], [1, 2], [2, 3] } .

I am using this for example to draw a line graph by connecting two points.

 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(); } } 

One, Two, MoreThanOne, AtLeast, 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(); } 

Duplicates

Used in conjunction with a method like Ani's AssertCount method (I use one called CountAtLeast ), it becomes very easy to find elements in a sequence that appear more than once:

 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

Optional Where clause on IEnumerable and IQueryable . Avoids if statements when building predicates & lambdas for a query. Useful when you don't know at compile time whether a filter should apply.

 public static IEnumerable<TSource> WhereIf<TSource>( this IEnumerable<TSource> source, bool condition, Func<TSource, bool> predicate) { return condition ? source.Where(predicate) : source; } 

Useage:

 var custs = Customers.WhereIf(someBool, x=>x.EyeColor=="Green"); 

LINQ WhereIf At ExtensionMethod.NET and borrowed from Andrew's blog .

ToList and ToDictionary with Initial Capacity

ToList and ToDictionary overloads that expose the underlying collection classes' initial capacity. Occasionally useful when source length is known or bounded.

 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; } 

Coalesce

 public static T Coalesce<T>(this IEnumerable<T> items) { return items.Where(x => x != null && !x.Equals(default(T))).FirstOrDefault(); // return items.OfType<T>().FirstOrDefault(); // Gabe's take }