您有一个简单的扩展签名:
public static IEnumerable<T> DefaultEnumerableIfEmpty<T>(this IEnumerable<T> enumerable, IEnumerable<T> defaultEnumerable)
如果初始集合为null或为空,则返回defaultEnumerable。默认枚举值可以是null、empty或非空(换句话说,它可以是您想要的任何值)。问题是:如何实现它一次运行?这可能吗?
我想出了这个解决方案:
public static IEnumerable<T> DefaultEnumerableIfEmpty<T>(this IEnumerable<T> enumerable, IEnumerable<T> defaultEnumerable)
{
if (enumerable != null)
{
var enumer = enumerable.GetEnumerator();
if (enumer.MoveNext())
{
yield return enumer.Current;
while (enumer.MoveNext())
{
yield return enumer.Current;
}
yield break;
}
}
return defaultEnumerable;//of course this will fail to compile
}
当然,它将无法编译。所以,这是个问题。
您有以下两个关键需求:
- 任何可枚举项都不应重复一次以上
- 如果原始可枚举对象为null或为空,并且
defaultEnumerable
为null
,则函数应返回null
(而不是空可枚举对象)
同时满足这两个要求并保持完全懒惰是不可能的。为了证明这一点,让我们假设一个非空的enumerable
(null
的情况可以很容易地单独处理,没有问题)。
为了知道enumerable是否为空,我们需要查看它,即尝试获取第一个元素。由于这将开始枚举可枚举项,因此我们需要完成它,以避免多次枚举它。
由于这是需要执行的逻辑,所以底层枚举器需要存在(这样它就可以执行代码,并尝试查看原始枚举器)。为了使该枚举器存在,必须有一个实际的对象封装在其中:枚举器。因此,有了这个逻辑,我们就无法返回null
。
唯一可行的方法是尽早执行逻辑。一个简单的解决方案是先将整个可枚举项读取到内存中:
public static IEnumerable<T> DefaultEnumerableIfEmpty<T>(this IEnumerable<T> enumerable, IEnumerable<T> defaultEnumerable)
{
if (enumerable == null)
return defaultEnumerable;
List<T> items = enumerable.ToList(); // enumerate it once
if (items.Count == 0)
return defaultEnumerable;
return items;
}
您还可以检索枚举器以立即读取第一个元素并评估逻辑,然后从该枚举器中重构一个枚举对象:
public static IEnumerable<T> DefaultEnumerableIfEmpty<T>(this IEnumerable<T> enumerable, IEnumerable<T> defaultEnumerable)
{
if (enumerable == null)
return defaultEnumerable;
var enumerator = enumerable.GetEnumerator();
if (enumerator.MoveNext())
return CombineBack(enumerator);
return defaultEnumerable;
}
private static IEnumerable<T> CombineBack<T>(IEnumerator<T> enumerator)
{
yield return enumerator.Current;
while (enumerator.MoveNext())
yield return enumerator.Current;
}
请注意,这实际上会立即开始迭代原始可枚举的。它并不像您期望的枚举和生成器函数那样完全懒惰。你只会在第一个元素之后得到懒惰。
如果你放松限制,你有两个选择。如果你接受偷看一次枚举,你可以使用Enumerable.Any()
:添加一个简单的检查
public static IEnumerable<T> DefaultEnumerableIfEmpty<T>(this IEnumerable<T> enumerable, IEnumerable<T> defaultEnumerable)
{
if (enumerable == null || !enumerable.Any())
return defaultEnumerable;
return enumerable;
}
或者,如果您接受返回一个空的可枚举对象而不是null
,则可以制作一个生成器,尝试对可枚举对象进行迭代并从中得出结果,如果原始可枚举对象中没有值,则返回到默认可枚举对象。这将始终返回一个非null枚举值,不过:
public static IEnumerable<T> DefaultEnumerableIfEmpty<T>(this IEnumerable<T> enumerable, IEnumerable<T> defaultEnumerable)
{
bool didYield = false;
if (enumerable != null)
{
foreach (var item in enumerable)
{
didYield = true;
yield return item;
}
}
if (!didYield && defaultEnumerable != null)
{
foreach (var item in defaultEnumerable)
yield return item;
}
}