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

Posted

技术标签:

【中文标题】C# 中是不是有“空列表”单例?【英文标题】:Is there an "Empty List" singleton in C#?C# 中是否有“空列表”单例? 【发布时间】:2012-01-23 06:15:19 【问题描述】:

在 C# 中,我经常使用 LINQ 和 IEnumerable。一切都很好(或至少大部分如此)。

但是,在许多情况下,我发现自己需要一个空的 IEnumerable<X> 作为默认值。也就是说,我愿意

for (var x in xs)  ... 

无需空检查即可工作。现在这是我目前所做的,具体取决于更大的背景:

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

现在,虽然上面的内容对我来说完全没问题——也就是说,如果创建数组对象有任何“额外开销”,我不在乎 -- 我在想:

C#/.NET 中是否存在“空的不可变 IEnumerable/IList”单例?(即使没有,是否有“更好”的方法来处理上述情况?) p>

Java 有 Collections.EMPTY_LIST 不可变单例——通过 Collections.emptyList<T>() “良好类型化”——它可以达到这个目的,尽管我不确定类似的概念是否甚至可以在 C# 中工作,因为泛型的处理方式不同。

谢谢。

【问题讨论】:

好吧,该死的 :) 这就是我关注 List/IList 而不是 Enumerable/IEnumerable 的结果,谢谢大家 -- 投票。 ***.com/questions/3229698/… public static class Array<T> public static readonly T[] Empty = new T[0]; 可以这样调用:Array<string>.Empty。我问了here in CodeReview。 【参考方案1】:

Enumerable.Empty<T>() 就是这样。

【讨论】:

嗯,不完全是。 :) 由于要求“列表”,Array.Empty<T>() 更准确,因为它是IList<T>。它还具有满足IReadOnlyCollection<T> 的快乐好处。当然,IEnumerable<T> 确实 就足够了,Enumerable.Empty<T>() 非常适合。 @Timo 这个问题以及包括这个问题在内的许多答案都是在Array.Empty<T> 可用之前大约 3 年写的。 :) 无论如何,尽管有标题,但问题清楚地表明IEnumerable<T> 有利于达到目的。【参考方案2】:

我想你正在寻找Enumerable.Empty<T>()

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

【讨论】:

【参考方案3】:

您正在寻找Enumerable.Empty<T>()

在其他新闻中,Java 空列表很糟糕,因为 List 接口公开了向列表添加元素的方法,这会引发异常。

【讨论】:

这是一个非常有效的观点!也许它需要一个新的 SO 问题? 你慢了几秒钟,没有文档链接 *finger shake* ;-) 但是接受扩展 svicks 评论。 另外,其他人得到了更多的支持,所以我们平分秋色:) 我会发布一个问题,但我现在要睡觉了,我将无法关注这个问题。为什么不自己发布呢? 因为一个类方法可能需要返回一个List(可以通过索引访问的集合)。那些List 可以是只读的。有时您需要返回这样一个只读的List,其中包含零元素。因此,Collections.emptyList() 方法。您不能只返回一个空的 Iterable,因为实现的接口指定该方法返回一个列表。最大的问题是它们没有可以返回的 ImmutableList 接口。 .NET 中也存在同样的问题:任何 IList 都可能是只读的。 附带说明,每个数组都是 C# 中的 IList,当您尝试添加新项目时也会抛出此错误。【参考方案4】:

我认为添加一个扩展方法是一个干净的选择,这要归功于它们处理空值的能力——比如:

  public static IEnumerable<T> EmptyIfNull<T>(this IEnumerable<T> list)
  
    return list ?? Enumerable.Empty<T>();
  

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

【讨论】:

【参考方案5】:

Microsoft 像这样实现了 `Any()' (source)

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()的扩展方法,只需重写进行这三个更改:

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

【讨论】:

【参考方案6】:

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

【讨论】:

【参考方案7】:

在您的原始示例中,您使用一个空数组来提供一个空的可枚举。虽然使用Enumerable.Empty&lt;T&gt;() 是完全正确的,但可能还有其他情况:如果您必须使用一个数组(或IList&lt;T&gt; 接口),您可以使用该方法

System.Array.Empty<T>()

这可以帮助您避免不必要的分配。

注释/参考:

documentation 没有提到该方法为每个类型只分配一次空数组 roslyn analyzers 建议使用此方法并带有警告 CA1825: Avoid zero-length array allocations 微软reference implementation .NET Core implementation

【讨论】:

感谢您指出文档与 roslyn 分析器不一致。你有没有在 GitHub 上打开一个问题来纠正这个问题?

以上是关于C# 中是不是有“空列表”单例?的主要内容,如果未能解决你的问题,请参考以下文章

C# 单例模式

C#单例模式的实现和性能对比

C#中各版本的单例模式

C#如何实现进程单例运行

设计模式之五:单例模式(Singleton Pattern)

设计模式-单例模式