你最喜欢的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<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); }
是空的
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.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
只返回指定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,2
和3,4
两个数组。 x.Chunks(2,true)
将返回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); }
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)); }
笔记:
- 由于返回的项目数是概率性的(可以很容易地在一个小序列上归零),所以对于微小的集合来说并不合适。
- 对于庞大的集合或数据库查询来说并不合适,因为它涉及列举序列中的每个项目。
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 }