c# – 如果该序列不为空,则使用IEnumerable序列作为参数调用方法

我有方法Foo,它进行一些CPU密集型计算并返回IEnumerable< T>序列.如果该序列为空,我需要检查.如果没有,请使用该序列作为参数调用方法Bar.

我想到了三种方法……

>使用Any()检查序列是否为空.这是好的,如果序列真的是空的,大多数时候都是这种情况.但它会有可怕的性能,如果序列将包含一些元素,Foo将需要它们再次计算…
>将序列转换为列表,检查该列表是否为空…并将其传递给Bar.这也有局限性.酒吧只需要前x个项目,所以Foo将做不必要的工作……
>检查,如果序列为空而没有实际重置序列.这听起来像双赢,但我找不到任何简单的内置方式,如何做到这一点.因此,我创建了这个模糊的解决方法,并想知道这是否真的是最好的方法.

条件

var source = Foo();

if (!IsEmpty(ref source))
    Bar(source);

将IsEmpty实现为

bool IsEmpty<T>(ref IEnumerable<T> source)
{
    var enumerator = source.GetEnumerator();

    if (enumerator.MoveNext())
    {
        source = CreateIEnumerable(enumerator);
        return false;
    }

    return true;

    IEnumerable<T> CreateIEnumerable(IEnumerator<T> usedEnumerator)
    {
        yield return usedEnumerator.Current;

        while (usedEnumerator.MoveNext())
        {
            yield return usedEnumerator.Current;
        }
    }
}

另请注意,使用空序列调用Bar不是选项…

编辑:
经过一番考虑后,我的案例的最佳答案来自Olivier Jacot-Descombes – 完全避免这种情况.接受的解决方案回答了这个问题 – 如果真的没有别的办法.

解决方法:

当且仅当可枚举源包含至少一个元素时,您想调用一些函数Bar< T>(IEnumerable< T> source),但是您遇到了两个问题:

> IEnumerable<T>中没有方法T Peek()所以你需要实际开始评估可枚举,看看它是否非空,但……
>您甚至不想部分地对可枚举进行双重评估,因为设置可枚举可能很昂贵.

在这种情况下,您的方法看似合理.但是,您确实存在一些问题:

>使用后需要dispose枚举器.
>如Ivan Stoevcomments中所指出的,如果Bar()方法试图评估IEnumerable< T>.不止一次(例如通过调用Any()然后调用foreach(…))然后结果将是未定义的,因为usedEnumerator将被第一个枚举耗尽.

要解决这些问题,我建议稍微修改您的API并创建一个扩展方法IfNonEmpty< T>(此IEnumerable< T>源,Action< IEnumerable< T>> func)仅在序列中调用指定方法是非空的,如下所示:

public static partial class EnumerableExtensions
{
    public static bool IfNonEmpty<T>(this IEnumerable<T> source, Action<IEnumerable<T>> func)
    {
        if (source == null|| func == null)
            throw new ArgumentNullException();
        using (var enumerator = source.GetEnumerator())
        {
            if (!enumerator.MoveNext())
                return false;
            func(new UsedEnumerator<T>(enumerator));
            return true;
        }
    }

    class UsedEnumerator<T> : IEnumerable<T>
    {
        IEnumerator<T> usedEnumerator;

        public UsedEnumerator(IEnumerator<T> usedEnumerator)
        {
            if (usedEnumerator == null)
                throw new ArgumentNullException();
            this.usedEnumerator = usedEnumerator;
        }

        public IEnumerator<T> GetEnumerator()
        {
            var localEnumerator = System.Threading.Interlocked.Exchange(ref usedEnumerator, null);
            if (localEnumerator == null)
                // An attempt has been made to enumerate usedEnumerator more than once; 
                // throw an exception since this is not allowed.
                throw new InvalidOperationException();
            yield return localEnumerator.Current;
            while (localEnumerator.MoveNext())
            {
                yield return localEnumerator.Current;
            }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }
}

演示小提琴单元测试here.

上一篇:C#foreach的本质是什么、如何实现自定义集合类的foreach遍历


下一篇:IEnumerator、IEnumerable傻傻分不清楚?