如果一次为空,则默认可枚举?

时间:2016-07-20 06:39:35

标签: c# performance linq

您有简单的扩展签名:

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
}

当然,它无法编译。所以,那是一个问题。

1 个答案:

答案 0 :(得分:3)

您有以下两个关键要求:

  • 不应该多次迭代枚举。
  • 如果原始枚举为null或为空,且nulldefaultEnumerable,则该函数应返回null(不是空的可枚举)。

不可能同时满足这两个要求并保持完全懒惰。为了证明这一点,我们假设一个非空enumerablenull案例可以单独处理,没有问题。)

为了知道可枚举是否为空,我们需要查看它,即尝试获取第一个元素。由于这会启动枚举的枚举,我们需要完成它以避免多次枚举。

由于这是需要执行的逻辑,因此底层枚举器需要存在(因此它可以执行该代码,并尝试查看原始可枚举)。为了使该枚举器存在,必须有一个封装在其中的实际对象:可枚举。因此,拥有此逻辑会阻止我们返回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;
    }
}