您有简单的扩展签名:
public static IEnumerable<T> DefaultEnumerableIfEmpty<T>(this IEnumerable<T> enumerable, IEnumerable<T> defaultEnumerable)
如果initial collection为null或为空,则返回defaultEnumerable。默认的枚举可以是null,空或非空(换句话说,它可以是您想要的任何值)。 Theres是一个问题:如何实现它一气呵成?这可能吗?
我提出了这个解决方案:
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
}
当然,它无法编译。所以,那是一个问题。
答案 0 :(得分:3)
您有以下两个关键要求:
null
为defaultEnumerable
,则该函数应返回null
(不是空的可枚举)。不可能同时满足这两个要求并保持完全懒惰。为了证明这一点,我们假设一个非空enumerable
(null
案例可以单独处理,没有问题。)
为了知道可枚举是否为空,我们需要查看它,即尝试获取第一个元素。由于这会启动枚举的枚举,我们需要完成它以避免多次枚举。
由于这是需要执行的逻辑,因此底层枚举器需要存在(因此它可以执行该代码,并尝试查看原始可枚举)。为了使该枚举器存在,必须有一个封装在其中的实际对象:可枚举。因此,拥有此逻辑会阻止我们返回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;
}
}