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

Nap*_*ppy 73 c# linq ienumerable extension-methods linq-to-objects

使用扩展方法,我们可以编写方便的LINQ运算符来解决泛型问题.

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

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

Tim*_*mwi 31

追加和前置

/// <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;
}
Run Code Online (Sandbox Code Playgroud)

  • Append和Prepend也可以用[AsEnumerable]实现(http://stackoverflow.com/questions/3645644/whats-your-favorite-linq-to-objects-operator-which-is-not-built-in/3645819# 3645819):`head.AsEnumerable().Concat(source)`/`source.Concat(element.AsEnumerable())` (16认同)
  • 您可以将'Append <T>`实现缩短为一行:`return source.Concat(Enumerable.Repeat(element,1));`. (7认同)
  • 好的一个+1,但是我将它从T更改为`params T []`,这样你就可以将一个或多个项目追加到最后. (2认同)

Ahm*_*eed 21

我很惊讶没有人提到过MoreLINQ项目.它由Jon Skeet创立,并在此过程中获得了一些开发人员.从项目的页面:

LINQ to Objects缺少一些理想的功能.

这个项目将以一种符合LINQ精神的方式,通过额外的方法增强LINQ to Objects.

查看Operators Overview wiki页面,获取已实现运算符的列表.

从一些干净优雅的源代码中学习它当然是一种很好的方法.


flq*_*flq 16

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

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

  • @Nappy:那个名叫`Select`并且它是内置的. (24认同)
  • Parallel.ForEach将做同样的并且能够并行执行.不是.. (3认同)
  • @Nappy:Do不等同于示例中的方法; 它必须跟随Run(),它还有一个带有Action <T>的重载.后者将等同于示例. (2认同)

Tim*_*mwi 14

ToQueue和ToStack

/// <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);
}
Run Code Online (Sandbox Code Playgroud)

  • @ck:你可以将相同的逻辑应用于内置扩展`ToList()`,这些扩展也很好地补充了`ToArray()`扩展.我更喜欢流利的`var myQueue = a.SelectMany(...).Where(...).OrderBy(...).ToQueue()`更传统的语法. (2认同)
  • @cjk我看到的最大优点是没有指定type参数.如果编译器可以推断它,我不想写`<ObjectType>`. (2认同)

Mas*_*gar 13

是空的

public static bool IsEmpty<T>(this IEnumerable<T> source)
{
    return !source.Any();
}
Run Code Online (Sandbox Code Playgroud)

  • +1我不知道为什么这会被投票.对我而言source.IsEmpty()比!source.Any()更清楚.我总是尽量避免使用!操作员尽可能在我看来很容易在快速扫描代码时跳过它. (6认同)
  • 这是内置的Any. (4认同)
  • `None`比`IsEmpty`更像是'Any`. (2认同)

Joh*_*ais 12

In和NotIn

两个其他众所周知的SQL结构的C#等价物

/// <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;
}
Run Code Online (Sandbox Code Playgroud)


Tim*_*mwi 9

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

  • .NET 4中的String.Join采用IEnumerable <T>. (4认同)

Ani*_*Ani 9

AsIEnumerable

/// <summary>
/// Returns a sequence containing one element.
/// </summary>
public static IEnumerable<T> AsIEnumerable<T>(this T obj)
{
    yield return obj;
}  
Run Code Online (Sandbox Code Playgroud)

用法:

var nums = new[] {12, 20, 6};
var numsWith5Prepended = 5.AsIEnumerable().Concat(nums);   
Run Code Online (Sandbox Code Playgroud)

  • 纯粹为了表现,我建议`返回新的T [] {obj};`而不是.这样,编译器不必构造整个状态机类只是为了产生一个值. (13认同)
  • 我觉得这个实现很危险.你对`new [] {1,2,3,4}有什么期望.AsIEnumerable()`?我期待1,2,3,4,而不是[1,2,3,4]. (4认同)
  • 我赞成编写EnumerableEx.Return(5).Concat(nums)而不是膨胀任何对象IntelliSense. (2认同)
  • 我更喜欢使用[`Append`和`Prepend`](http://stackoverflow.com/questions/3645644/whats-your-favorite-linq-to-objects-operator-which-is-not-built-in/3645715 #3645715)为此. (2认同)

Tim*_*mwi 8

订购

/// <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);
}
Run Code Online (Sandbox Code Playgroud)

  • 我宁愿称这种方法为'排序'. (8认同)
  • @Steven:'Sort'会导致`List <T> .Sort()`的歧义 (4认同)
  • @Steven:是的,但无论如何,对于阅读代码的人来说,它仍然是模棱两可的.差异很重要,因为`List <T> .Sort`是就地的. (4认同)

Han*_*man 8

拖曳

public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> items)
{
    var random = new Random();
    return items.OrderBy(x => random.Next());
}
Run Code Online (Sandbox Code Playgroud)

编辑:上面的实现似乎有几个问题.这是@ 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--;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 你的实现是错误的。更糟糕的是,这是微妙的错误。首先,它有一个隐藏的全局依赖:随机源(更糟糕的是,如果快速调用多次,你选择的源将给出相同的随机数!)。其次,使用的算法不好。它不仅比 Fisher-Yates 渐进慢,而且不统一(分配相同键的元素保持相同的相对顺序)。 (2认同)
  • 我可能会添加随机源作为参数。这有两个优点:您不会在启动时创建多个 Random 源,或者至少是开发人员正确初始化它们的响应能力,其次,如果持续应用它是一个很好的指标,该方法每个返回不同/随机结果时间。 (2认同)

Tim*_*mwi 7

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;
    }
}
Run Code Online (Sandbox Code Playgroud)


Dan*_*Tao 7

这是我刚才想到的一个很酷的一个.(如果我只是想到它,也许它不是那么有用?但我想到了它,因为我对它有用.)重复循环一个序列以产生无限序列.这可以实现某些类似于什么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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

var numbers = new[] { 1, 2, 3 };
var looped = numbers.Loop();

foreach (int x in looped.Take(10))
{
    Console.WriteLine(x);
}
Run Code Online (Sandbox Code Playgroud)

输出:

1
2
3
1
2
3
1
2
3
1

注意:我想你也可以通过以下方式完成此任务:

var looped = Enumerable.Repeat(numbers, int.MaxValue).SelectMany(seq => seq);
Run Code Online (Sandbox Code Playgroud)

......但我认为Loop更清楚.


Tim*_*mwi 6

指数

/// <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;
}
Run Code Online (Sandbox Code Playgroud)

  • 你应该调用这个`FindIndex`来匹配`List <T>`和`Array`上执行相同操作的方法.我还会考虑检查`source`是否已经实现它并调用本机`FindIndex`函数之一(尽管由于你没有过载而在性能方面没有太大差别)这需要一个起始索引). (5认同)

Las*_*olt 6

大块

返回特定大小的块.x.Chunks(2)1,2,3,4,5将返回两个数组与1,23,4.x.Chunks(2,true)将返回1,2,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();
}
Run Code Online (Sandbox Code Playgroud)

  • @Nappy:[它*是*IEnumerable <IEnumerable <T >>`](http://msdn.microsoft.com/en-us/library/dd233059.aspx). (2认同)

Han*_*man 6

ToHashSet

public static HashSet<T> ToHashSet<T>(this IEnumerable<T> items)
{
    return new HashSet<T>(items);
}
Run Code Online (Sandbox Code Playgroud)


Tim*_*mwi 5

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Tim*_*mwi 5

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);
}
Run Code Online (Sandbox Code Playgroud)


Ani*_*Ani 5

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>();
}    
Run Code Online (Sandbox Code Playgroud)

用法:

foreach(var item in myEnumerable.EmptyIfNull())
{
  Console.WriteLine(item);   
}
Run Code Online (Sandbox Code Playgroud)


Dan*_*Tao 5

解析

这个涉及一个自定义委托(可能已经使用了一个IParser<T>接口,但我使用了委托,因为它更简单),它用于将一系列字符串解析为一系列值,跳过解析失败的元素.

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

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);
}
Run Code Online (Sandbox Code Playgroud)

输出:

1
2
4
6

这个命名很棘手.我不确定是否Parse是最好的选择(至少简单的),或者是否ParseWhereValid会更好.

  • @Nappy:是的,我喜欢`TryParse`; 我唯一关心的是有人可能会期望它返回一个`bool`并填充一个`out IEnumerable <T>`参数(如果*every*item被解析则只返回true).也许`ParseWhereValid`是最好的. (2认同)