C#中是否有“空列表”单例?

Is there an “Empty List” singleton in C#?

在C中,我很好地使用LINQ和IEnumerable。一切都很好(至少大部分是这样)。

然而,在许多情况下,我发现自己需要一个空的IEnumerable作为默认值。也就是说,我想

1
for (var x in xs) { ... }

不需要空检查就可以工作。这就是我目前所做的,取决于更大的背景:

1
2
var xs = f() ?? new X[0];              // when xs is assigned, sometimes
for (var x in xs ?? new X[0]) { ... }  // inline, sometimes

现在,虽然上面的内容对我来说非常好——也就是说,如果在创建数组对象时有任何"额外开销",我只是不在乎——我想知道:

C/.NET中是否存在"Empty Immutable IEnumerable/IList"单例?(即使没有,是否有更好的方法来处理上述情况?)

Java具有EDCOX1,1,不可变的单体——"EDCX1",通过EDCOX1,2,-这是为了这个目的,虽然我不确定一个类似的概念是否能在C**中工作,因为泛型的处理方式不同。

谢谢。


你在找Enumerable.Empty();

在其他新闻中,Java空列表很糟糕,因为列表接口公开了将元素添加到抛出异常的列表中的方法。


Enumerable.Empty()正是这样。


我想你在找Enumerable.Empty()

空列表单例没有那么多意义,因为列表通常是可变的。


我认为添加一个扩展方法是一个干净的选择,因为它们能够处理空值,比如:

1
2
3
4
5
6
7
8
9
  public static IEnumerable<T> EmptyIfNull<T>(this IEnumerable<T> list)
  {
    return list ?? Enumerable.Empty<T>();
  }

  foreach(var x in xs.EmptyIfNull())
  {
    ...
  }

在原始示例中,使用空数组提供空的可枚举数组。虽然使用Enumerable.Empty()是完全正确的,但也可能存在其他情况:如果必须使用数组(或IList接口),则可以使用该方法

1
System.Array.Empty<T>()

这有助于避免不必要的分配。

注释/参考:

  • 文档没有提到这个方法只为每种类型分配一次空数组。
  • Roslyn分析器建议使用警告CA1825的方法:避免零长度数组分配
  • Microsoft参考实现
  • .NET核心实现

使用带列表的Enumerable.Empty()有一个缺点。如果将Enumerable.Empty交给列表构造函数,则会分配一个大小为4的数组。但是,如果将一个空的Collection交给列表构造函数,那么就不会发生分配。因此,如果在代码中使用此解决方案,那么很可能会使用IEnumerable中的一个来构建列表,从而导致不必要的分配。


Microsoft实现了类似于此的"any()"(源代码)

1
2
3
4
5
6
7
8
9
public static bool Any<TSource>(this IEnumerable<TSource> source)
{
    if (source == null) throw new ArgumentNullException("source");
    using (IEnumerator<TSource> e = source.GetEnumerator())
    {
        if (e.MoveNext()) return true;
    }
    return false;
}

如果要在调用堆栈上保存调用,而不是编写调用!Any()的扩展方法,只需重写并进行以下三项更改:

1
2
3
4
5
6
7
8
9
public static bool IsEmpty<TSource>(this IEnumerable<TSource> source) //first change (name)
{
    if (source == null) throw new ArgumentNullException("source");
    using (IEnumerator<TSource> e = source.GetEnumerator())
    {
        if (e.MoveNext()) return false; //second change
    }
    return true; //third change
}