我有方法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 Stoev在comments中所指出的,如果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.