在我的代码中,我需要使用IEnumerable<>几次,导致ReSharper错误“可能的IEnumerable多重枚举”。

示例代码:

public List<object> Foo(IEnumerable<object> objects)
{
    if (objects == null || !objects.Any())
        throw new ArgumentException();
        
    var firstObject = objects.First();
    var list = DoSomeThing(firstObject);        
    var secondList = DoSomeThingElse(objects);
    list.AddRange(secondList);
    
    return list;
}

我可以将对象参数改为List,然后避免可能的多重枚举,但这样我就得不到我能处理的最高的对象。 我能做的另一件事是在方法的开头将IEnumerable转换为List:


 public List<object> Foo(IEnumerable<object> objects)
 {
    var objectList = objects.ToList();
    // ...
 }

但这太尴尬了。

在这种情况下你会怎么做?


当前回答

首先,这种警告并不总是意义重大。我通常在确定它不是性能瓶颈后禁用它。它只是意味着IEnumerable被求值两次,这通常不是问题,除非求值本身需要很长时间。即使它确实需要很长时间,在这种情况下,您第一次只使用一个元素。

在这种情况下,您还可以更多地利用强大的linq扩展方法。

var firstObject = objects.First();
return DoSomeThing(firstObject).Concat(DoSomeThingElse(objects).ToList();

在这种情况下,可能只计算IEnumerable一次,但会有一些麻烦,但先进行概要分析,看看这是否真的是个问题。

其他回答

如果你只需要检查第一个元素,你可以在不迭代整个集合的情况下查看它:

public List<object> Foo(IEnumerable<object> objects)
{
    object firstObject;
    if (objects == null || !TryPeek(ref objects, out firstObject))
        throw new ArgumentException();

    var list = DoSomeThing(firstObject);
    var secondList = DoSomeThingElse(objects);
    list.AddRange(secondList);

    return list;
}

public static bool TryPeek<T>(ref IEnumerable<T> source, out T first)
{
    if (source == null)
        throw new ArgumentNullException(nameof(source));

    IEnumerator<T> enumerator = source.GetEnumerator();
    if (!enumerator.MoveNext())
    {
        first = default(T);
        source = Enumerable.Empty<T>();
        return false;
    }

    first = enumerator.Current;
    T firstElement = first;
    source = Iterate();
    return true;

    IEnumerable<T> Iterate()
    {
        yield return firstElement;
        using (enumerator)
        {
            while (enumerator.MoveNext())
            {
                yield return enumerator.Current;
            }
        }
    }
}

在这种情况下,我通常用IEnumerable和IList重载我的方法。

public static IEnumerable<T> Method<T>( this IList<T> source ){... }

public static IEnumerable<T> Method<T>( this IEnumerable<T> source )
{
    /*input checks on source parameter here*/
    return Method( source.ToList() );
}

我注意在方法的摘要注释中解释了调用IEnumerable将执行. tolist()。

如果多个操作被连接在一起,程序员可以在更高的级别上选择. tolist(),然后调用IList重载或让IEnumerable重载来处理。

如果目的真的是为了防止多重枚举,那么Marc Gravell的答案是值得阅读的,但保持相同的语义,你可以简单地删除冗余的Any和First调用,并使用:

public List<object> Foo(IEnumerable<object> objects)
{
    if (objects == null)
        throw new ArgumentNullException("objects");

    var first = objects.FirstOrDefault();

    if (first == null)
        throw new ArgumentException(
            "Empty enumerable not supported.", 
            "objects");

    var list = DoSomeThing(first);  

    var secondList = DoSomeThingElse(objects);

    list.AddRange(secondList);

    return list;
}

注意,这假设你IEnumerable不是泛型的,或者至少被限制为引用类型。

.NET 6/ c# 10

. .除此之外,您可以尝试确定序列中的元素数量,而无需使用Enumerable强制枚举。TryGetNonEnumeratedCount(IEnumerable, Int32)方法。

如果source的计数可以在没有枚举的情况下确定,则此方法返回true;否则,假的。因此,您可以检查是否需要进一步实现。

using System;
using System.Collections.Generic;
using System.Linq;
                    
public class Program
{
    public static void Main()
    {
        IEnumerable<int> arrayOne = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

        var canGetCountDirectly = arrayOne.TryGetNonEnumeratedCount(out int theCount);

        Console.WriteLine($"Count can be returned directly = {canGetCountDirectly}");
        Console.WriteLine($"Count = {theCount}");
    }
}

首先,这种警告并不总是意义重大。我通常在确定它不是性能瓶颈后禁用它。它只是意味着IEnumerable被求值两次,这通常不是问题,除非求值本身需要很长时间。即使它确实需要很长时间,在这种情况下,您第一次只使用一个元素。

在这种情况下,您还可以更多地利用强大的linq扩展方法。

var firstObject = objects.First();
return DoSomeThing(firstObject).Concat(DoSomeThingElse(objects).ToList();

在这种情况下,可能只计算IEnumerable一次,但会有一些麻烦,但先进行概要分析,看看这是否真的是个问题。