您最喜欢的不是内置的LINQ to Objects运算符是什么?[关闭]


71

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

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

最好使用可能使用现有方法的简洁明了的实现。


到目前为止,您似乎大多数实现都选择减少开销,而不是清洁和优雅,但是对我个人而言,这使它们更有用。
罗曼·斯塔科夫

能够折叠所有代码块在此页面上非常有用
☺– Timwi

extensionmethod.net上有很多-VB和C#示例。
p.campbell

3
这个问题可能应该被锁定,例如stackoverflow.com/q/101268/344286
Wayne Werner

Answers:


31

追加和前置

(自编写此答案以来,这些内容已添加到.NET。)

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

您可以通过添加枚举值的位置,然后添加“之间”来进一步概括。我需要在集合的所有值之间注入一个值,类似于String.Join类型的功能,仅使用泛型。
Lasse V. Karlsen

@Lasse V. Karlsen:我已经发布了一个InsertBetween方法作为单独的答案。
Timwi's

7
您可以将'Append <T>`实现缩短为一个衬里:return source.Concat(Enumerable.Repeat(element, 1));
史蒂文2010年

16
Append和Prepend也可以通过AsEnumerable实现:head.AsEnumerable().Concat(source)/source.Concat(element.AsEnumerable())
Nappy 2010年

2
好的+1,但我将其从T更改为,params T[]以便您可以在末尾附加一个或多个项目。

21

令我惊讶的是,还没有人提到MoreLINQ项目。它由乔恩·斯凯特Jon Skeet)创立,并在此过程中吸引了一些开发人员。从项目页面:

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

该项目将以额外的方法增强LINQ to Objects,并保持LINQ的精神。

查看Operators Overview Wiki页面,了解已实施的运营商的列表。

当然,这是学习一些简洁优雅的源代码的好方法。


16

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

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

3
Parallel.ForEach将做同样的事情并且能够并行执行。是不是..
Abhishek 2010年

1
重载采用func而不是action和yield return的结果也是显而易见的。
Nappy

25
@Nappy:有人打来的Select,是内置的。–
Timwi

1
.NET(Rx)的Reactive Extensions的System.Interactive.dll的一部分称为Do:“针对序列中的每个值调用其副作用的动作。”
Nappy

2
@Nappy:Do不等同于示例中的方法;它之后必须是Run(),它还有一个重载,该重载需要一个Action <T>。后者将等同于示例。
Markus Johnsson'9

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

正义到底有什么问题var myQueue = new Queue<ObjectType>(myObj);?对于仅一行来说,这并不是真正值得的扩展……
cjk 2010年

2
@ck:您可以将相同的逻辑应用于内置扩展,ToList()并且这些扩展也ToArray()很好地补充了扩展。var myQueue = a.SelectMany(...).Where(...).OrderBy(...).ToQueue()与更传统的语法相比,我更喜欢流利的语言。
Martin Liversage 2010年

1
@Martin(&TimwI)-将大量运算符链接在一起时,我可以理解这一点,这种方式整洁得多。+1。
cjk 2010年

2
@cjk我看到的最大优点是未指定type参数。<ObjectType>如果编译器可以推断出它,我不想在那儿写。
nawfal 2014年

13

是空的

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

6
+1我不确定为什么不赞成。对我来说source.IsEmpty()比!source.Any()更容易阅读。我总是尽量避免使用!我认为,在快速扫描代码时,可以轻松跳过该操作符。
Bear Monkey 2010年

3
NoneAny比更加类似于IsEmpty
nawfal 2014年

12

在和不在

其他两个著名的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;
}

而不是if (values == null) return false;我认为应该抛出异常。悄无声息地吞并错误情况永远都不是好事。
Timwi's

这取决于你怎么看了。事实是元素永远不会包含在空值列表中。
约翰·布莱斯

一件事,有助于DRY,但会增加调用堆栈;params数组(即数组)是IEnumerable,因此您的params重载可以简单地调用IEnumerable重载。
KeithS 2012年

1
仅仅return values.Contains(value);是不够的。
nawfal 2014年

9

可数

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

2
我更喜欢编写EnumerableEx.Return(5).Concat(nums)而不是使任何对象智能感知膨胀。
Nappy

2
我更喜欢使用AppendPrepend为此。
Timwi's

14
纯粹出于性能考虑,我建议return new T[] { obj };使用。这样,编译器不必为了产生一个值而构造整个状态机类。
克里斯蒂安·海特

4
我觉得这种实施很危险。您会期望new[]{1, 2, 3, 4}.AsIEnumerable()什么?我期望1,2,3,4,而不是[1,2,3,4]。
larsmoa 2011年

1
@larsm:这就是大多数库都使用的原因EnumerableEx.Return(new[]{1, 2, 3, 4})。没错,“ As”表示正在进行一些强制转换,并且由于数组已经实现了IEnumerable,因此您期望什么都不会改变。
纳皮

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

1
非常有用,尽管就我个人而言,我会取出所有格式代码,并且只能够IEnumerable<string>使用分隔符进行连接。您始终可以IEnumerable<string>在调用此方法之前将数据投影到其中。
Christian Hayter 2010年

@Timwi:有几点要点:null附加到之前不需要检查StringBuilder。您需要处置枚举器。您可以摆脱循环Append上方的调用,并将while循环替换为do-while。最后,除非要避免支付创建a的成本,否则StringBuilder不需要将第一个元素视为特殊情况:new StringBuilder().ToString()will return string.Empty
阿妮2010年

4
.NET 4中的String.Join采用IEnumerable <T>。
伊恩·默瑟

1
这会做我们Aggregate操作员还没有做的事情吗?这是一种不寻常的用法,但是绝对可以用于连接对象列表。
柯克·布罗德赫斯特

1
@Kirk Broadhurst:好吧,其中之一是,Aggregate如果在每个阶段连接字符串而不是使用StringBuilder ,则速度会变慢。但是对于两个人,即使我想使用Aggregate它,我仍然会将其包装到JoinString带有此签名的方法中,因为它使使用它的代码更加清晰。一旦有了,我不妨通过使用StringBuilder来编写更快的方法。
Timwi's

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

8
我宁愿将此方法称为“排序”。
史蒂文

4
@Steven:“排序”将与List<T>.Sort()
Ani

1
现在不会了,因为C#编译器将始终在扩展方法之前选择实例方法。
史蒂文

4
@Steven:是的,但是无论如何,对于阅读代码的人来说仍然是模棱两可的。区别很重要,因为List<T>.Sort它就位。
Timwi'9

1
这是否不要求GTC要求T实施IComparable?
KeithS

8

随机播放

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

我建议将其重命名为Randomize或Shuffle。
Nappy

使用静态Random对象可能会更好。
cjk 2010年

2
您的实现是错误的。更糟糕的是,这是错误的。首先,它具有隐藏的全局依赖性:随机性的来源(甚至更糟糕的是,如果快速调用多次,您选择的来源将产生相同的改组)。其次,使用的算法不好。它不仅在渐近上比Fisher-Yates慢,而且不统一(分配了相同键的元素以相同的相对顺序排列)。
Craig Gidney 2010年

2
我可能会添加随机源作为参数。这有两个优点:您不必在启动时创建多个随机数源,或者至少是开发人员正确初始化它们的响应能力;其次,如果始终如一地应用其良好指示符,则该方法将分别返回不同/随机结果时间。
尿布

1
@Nappy不幸的是,.NET中的大多数随机性源都没有相同的外部接口。将Math.Random与System.Security.Cryptography.RandomNumberGenerator的实现进行对比。您可以编写适配器和/或接受一个简单的适配器,Func<int>但必须由他人来简化该方法获取其PRN的方式。
KeithS 2015年

7

循环

我刚刚想到的这有点酷。(如果我只是想到了,也许它没有那么有用?但是我想到了它,因为我有用。)重复遍历一个序列以生成一个无限序列。除了可以用于任意序列(不同于)(不同于)之外,这可以完成某种工作Enumerable.RangeEnumerable.Repeat为您提供:RangeRepeat

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更清楚。


此实现具有一个有趣的属性,它每次都会创建一个新的枚举数,因此它可能会产生意外的结果。
加布

@Gabe:如果没有每次都创建一个新的枚举器,实际上是没有办法的。我的意思是,您可以调用Reset枚举器;但我敢肯定,有90%的IEnumerator<T>实现都不受支持。因此,我猜您可能会获得“意外的”结果,但前提是您期望某些不可行的结果。换句话说:用于任何有序序列(如T[]List<T>Queue<T>等),该命令将是稳定的; 对于任何无序序列,您都不应该期望它是(在我看来)。
丹涛

@Gabe:我想,另一种选择是接受一个可选bool参数,该参数指定该方法是否应缓存第一个枚举的顺序,然后对其进行循环。
丹涛

丹:如果您每次都需要从循环中获得相同的结果,则可以只使用x.Memoize().Loop(),但这当然意味着您首先需要一个Memoize函数。我同意可选的bool可以,也可以使用单独的方法,例如LoopSameLoopDeterministic
加布

7

最小元素

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

1
最好使valueSelectorreturnIComparable或更改valueSelector为类似的东西,Func<T, T, bool> lessThan以便可以比较诸如字符串或小数之类的东西。
加布

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

5
您应该调用它FindIndex来匹配方法,List<T>并且Array做同样的事情。我还将考虑检查是否source已实现它并调用本机FindIndex函数(尽管这不会对性能产生太大影响,因为您没有带起始索引的重载),这是其中之一。 。
加布

6

大块

返回特定大小的块。x.Chunks(2)of1,2,3,4,5将返回带有1,2和的两个数组3,4x.Chunks(2,true)将返回1,23,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();
}

@Timwi感谢您指出这一点。我通常使用一个返回List的数组,但将其更改为返回我认为最多的数组。立即更正:)
Lasse Espeholt 2010年

一个替代名称是Buffer
Nappy

为什么返回数组?我更喜欢IEnumerable <IEnumerable <T >>
Nappy


@Nappy纯IEnumerable版本会慢一些(至少对于小块而言)。但是如上所述,我已经使用了许多上述版本。而且,您可以随时使用数组,IEnumerable因为数组可以继承IEnumerable
Lasse Espeholt

6

ToHashSet

public static HashSet<T> ToHashSet<T>(this IEnumerable<T> items)
{
    return new HashSet<T>(items);
}

我将使它返回ISet <T>并将其称为ToSet。这样可以更好地隐藏内部实现。
realbart '16

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

1
它只是发生,我认为这可以称为FirstOr作为val = list.FirstOr(defaultVal)
加布

1
我喜欢的名字FirstOrFallback
奥马尔雷维吾

5

插入之间

在每对连续的元素之间插入一个元素。

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

1
尽管这似乎不太令人费解,但我没有看到用例。你能说一些吗?谢谢。
斯特凡纳·古里科

5

空如果空

这是一个有争议的。我相信许多纯粹主义者将在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);   
}

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

用法:

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会更好。


1
TryParseParseWhereValid 最适合imo。;)
Nappy

2
@Nappy:是的,我喜欢TryParse;我唯一担心的是有人可能希望它返回abool并填充一个out IEnumerable<T>参数(仅在解析了每个项目时才返回true )。也许ParseWhereValid是最好的。
丹涛

4

邮编合并

这是我的版本,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;
    }
}

有用,但也许应该被称为内置以外的其他名称Zip?(我知道参数足以区分,但
出于

@Timwi关于其他名称的任何建议吗?也许ZipMerge?
Nappy

这不会跳过的第一个元素second,还是我误读了代码?
Aistina'9

@Aistina:我认为您对的误解MoveNext。第一次调用将告诉您该枚举数是否为空。Current然后包含第一个元素。
Nappy

1
@realbart那不是真的:“如果MoveNext通过集合的末尾,则枚举数将位于集合中最后一个元素之后,并且MoveNext返回false。当枚举数位于此位置时,对MoveNext的后续调用也将返回false,直到Reset为叫。” 参见msdn.microsoft.com/library/…–
Nappy

4

随机抽样

这是一个简单的函数,如果您有一组中等大小的数据(例如,超过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. 确实不适合大型集合或数据库查询,因为它涉及枚举序列中的每个项目。

有趣的是,尽管通常要求X个随机元素比说“随机给我大约X%的元素”有用。为此,您应该执行以下操作:source.OrderBy(r.NextDouble()).Take(x);
mattmc3 2010年

4

断言计数

有效地确定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

@Timwi:不幸的是,如果Enumerable.Count<T>()无法以这种方式确定计数,它将枚举整个序列。这将破坏此扩展的要点,后者可能会快速失败和快速通过。
阿妮(Ani)2010年

您应该添加类似的内容,CountAtMost这样我就可以根据是否存在,单数或复数元素来更改操作,而不必创建3个枚举数或对所有元素进行计数。
加布

@Gabe:对不起,但是我不太明白。有一个CountAssertion.AtMost。还是我错过了什么?
阿妮2010年

1
顺便说一句,我不喜欢该Assertion命名法,因为Assert对我而言,该词暗示其条件为假时会引发异常。
加布

2
阿仁:我想可能有3种不同的方法:CountIsCountIsAtMost,和CountIsAtLeast
加布

4

窗口

枚举长度为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();
    }
}

4

一,二,多于,至少,任意

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

不要忘记将枚举器包装在using语句中。
Bear Monkey 2010年

我已将其删除,ToEnumerable因为它似乎与其他人无关,并且无论如何它已经发布在另一个答案中。
Timwi's

好吧,Timwi,如果您仔细研究了ToEnumerable的实现,您会发现它们并不相同。我的版本采用了任意设置,并变成了可枚举的。这与其他人发布的功能不同。但是请确保...继续自由。
noopman 2010年

1
+1我喜欢这些方法。我在自己的utils库中定义了类似的方法。如果将其反转并称为IsNullOrEmpty,则IMO AnyAtAll会更容易理解。
熊猴

1
我自己的扩展方法库中有一个非常类似的“一个”函数。重载这些以接受投影(如Any,First等所做的那样)也可能很有用。
KeithS 2015年

3

跳过最后和最后一次

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

我相信您的实现TakeLast可以用于SkipLastyield return queue.Dequeue();
Mark Hurd

3

重复项

与Ani的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);
}

我认为您可以g.CountAtLeast(2)使用“内置” LINQ编写g.Skip(1).Any()
Timwi's

@Timwi:这就是我的写法;)我使用的几种扩展方法实际上只是围绕功能的非常薄的包装,这些功能已经可以简洁地编写(另一个示例:SkipNulls(),仅仅是Where(x => x != null))。我之所以使用它们,不是因为它们做了很多额外的工作,而是因为我发现它们使代码更具可读性(无论如何,将几个方法调用包装到一个适合代码重用的地方也不是一件坏事)。
丹涛

丹:SkipNulls<T>()确实是OfType<T>()
加布

@Gabe:你可以把它OfType<T>Where<T>; 无论哪种方式,它都只是一个琐碎的包装器。我的意思是,这个名称SkipNulls使它更具用途。
丹涛

3

如果

可选Where的条款IEnumerableIQueryable。在为查询构建谓词和lambda时避免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在ExtensionMethod.NET并从Andrew的博客借来


有趣。您可以使用它来将复选框链接到AJAX形式的搜索结果页面中的过滤器;mySearchResults.WhereIf(chkShowOnlyUnapproved.Checked, x=>!x.IsApproved)
KeithS

3

具有初始能力的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;
}

2

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

1
在语义上与相同collection.Take(maxCount).Count(),对吗?
Timwi

2

合并

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
}

由于您实际上正在使用它,请允许我为您简化它。
加布

不用了,谢谢。由于您的版本仅适用于对象类型(不适用于DateTime等),因此对OfType <T>的使用并不常见,因为大多数人都将其视为强制转换方法,而不是非null过滤器,并且它不适用在可读性或性能方面添加任何内容,我会坚持使用原版。
mattmc3'9

但是OfType <T>可以与DateTime一起使用,不是吗?另外,我确实将OfType <T>视为非null过滤方法,否则它将如何工作?也许只是我和加伯...
猴子

当然,OfType适用于值类型。尽管Gabe的用法更改了Coalesce方法,所以它不适用于我希望值类型使用的方式。就OfType而言,我认为它的用法更多用于要按类型过滤的异构或多态集合(msdn.microsoft.com/en-us/library/bb360913.aspx)。MSDN文章甚至没有提到过滤出空值。
mattmc3 2010年

从VB转换为C#时犯了经典错误……Nothing不等同于null……不得不修改C#实现。
mattmc3'9
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.