C#/.NET中Java Collections.<T>emptyList()方法的等效方法是什么?

41

在C#中,获取一个类型化且只读的空列表的标准方式是什么,还是有其他方法吗?

注:对于那些问“为什么”的人:我有一个虚拟方法返回一个 IList(或更确切地说,是一个IEnumerable),默认实现为空。无论列表返回什么内容都应该是只读的,因为写入它将是一个错误,如果有人尝试写入,我希望立即停止并捕获错误,而不是等待错误以某种微妙的方式出现。


4
你会拿一个只读的空列表做什么呢?只是好奇。 - goenning
1
只是好奇,为什么你需要一个空的只读列表? - ps.
在这种情况下,IEnumerable<T> 很好用,谢谢。 - David Moles
我认为需要返回空的IList有点奇怪。从函数角度来看,列表是一个序列。然而,在C#中,IList是可以添加元素的。我猜你应该返回Enumerable.Empty<T>(),这将给出一个空序列。 - Tormod
2
如果IList只是一个序列,我们就不需要它了,我们只需使用IEnumerable。如果您想要任何ICollection方法,则至少需要ICollection,如果您想要索引访问,则需要IListICollection<T>中的修改操作是可选的,这就是为什么有IsReadOnly()以及为什么Add()等被记录为抛出NotSupportedException的原因。 - David Moles
显示剩余2条评论
8个回答

29

个人认为,这比其他任何答案都更好:

static readonly IList<T> EmptyList = new T[0];
  • 数组实现了 IList<T> 接口。
  • 不能在数组中添加元素。
  • 无法对空数组中的元素进行赋值(因为不存在任何元素)。
  • 在我看来,这要比 new List<T>().AsReadOnly() 简单得多。
  • 仍然可以返回一个 IList<T> (如果需要的话)。

顺便提一下,如果我没记错的话,Enumerable.Empty<T>() 实际上就是使用了这种方式。所以从理论上讲,你甚至可以这样做:(IList<T>)Enumerable.Empty<T>() (尽管我认为没有太好的理由这样做)。


您的回答对我来说只有在附加到Virtlink的回答的评论方面才有意义。 - jpaugh
5
自 4.6 版本以来,您甚至不需要再提供自己的对象,只需返回 Array.Empty<T>() 即可。 - ZunTzu

26

你可以创建一个列表:

List<MyType> list = new List<MyType>();

如果你想要一个空的 IEnumerable<T>,使用 Enumerable.Empty<T>()

IEnumerable<MyType> collection = Enumerable.Empty<MyType>();

如果你真正想要一个只读列表,可以这样做:

IList<MyType> readonlyList = (new List<MyType>()).AsReadOnly();

这将返回一个ReadOnlyCollection<T>,它实现了IList<T>


2
我也曾这么想过,但是后来看到了只读的要求。然后我又想,一个空的只读 List<T> 没有什么意义吧?我错了什么? - Jay Riggs
3
一个空的只读集合在 API 返回值中很理想,可以传达“没有结果”的概念,而不需使用 null。理想情况下,你会希望出于内存节省的原因而引用相同的只读空集合,就像使用 string.Empty 一样。 - jpaugh

18

从 .net 4.6 开始,您还可以使用以下内容:

IList<T> emptyList = Array.Empty<T>();

这只会为您指定的每种不同类型 T 创建一个新实例。


1
就像String.Empty一样,它也总是引用相同的实例而不是创建一个新的空字符串。 - David De Sloovere
3
@David De Sloovere,空字符串文字""也由于字符串池化而引用相同的实例(仅供那些感觉有必要用String.Empty替换""的读者参考)。 - ZunTzu

9
IList<T> list = new List<T>().AsReadOnly();

或者,如果您想要一个 IEnumerable<>

IEnumerable<T> sequence = Enumerable.Empty<T>();

1
自4.6版本以来,有一个更好的解决方案:IList<T> list = Array.Empty<T>(); - ZunTzu

4
如果你想要一个内容无法被修改的列表,可以这样做:
ReadOnlyCollection<Foo> foos = new List<Foo>().AsReadOnly();

2

2

进一步扩展丹·陶(Dan Tao)的答案,可以使用以下实现方式,以与Enumerable.Empty<T>()相同的方式使用,只需指定List.Empty<T>()即可。

public static class List
{
    public static IList<T> Empty<T>()
    {
        // Note that the static type is only instantiated when
        // it is needed, and only then is the T[0] object created, once.
        return EmptyArray<T>.Instance;
    }

    private sealed class EmptyArray<T>
    {
        public static readonly T[] Instance = new T[0];
    }
}

编辑:我根据与Dan Tao关于Instance字段的延迟初始化和急切初始化的讨论,更改了上面的代码以反映结果。


零长度数组的惰性初始化?我个人认为这是一个错误,因为它增加了复杂性,会稍微降低性能(每次访问都需要额外的方法调用),存在竞争条件(两个线程同时调用List.Empty<T>()可能会得到不同的对象),并且几乎没有什么好处(你认为单个零长度数组的初始化成本与JIT编译新类的成本相比如何?)。使用急切初始化的static readonly公共字段,或者-如果你的良心不允许-一个私有字段加上Empty方法。只是我的个人意见! - Dan Tao
1
两个线程获取不同的对象并不是一个问题。性能不受影响,因为调用很可能会被内联处理。我不明白一行实际逻辑怎么会严重增加复杂性。最后但并非最不重要的:我刚刚检查了Enumerable.Empty<T>()的实现,它与我所写的非常相似。如果需要在各处都使用空列表,可以使用这种方法以节省内存分配和垃圾回收。如果只偶尔需要一个空列表,请使用您的static readonly - Daniel A.A. Pelsmaeker
你保持警觉可能是正确的,因为我的评论确实有些吹毛求疵。然而,如果您有一些依赖于检查if (list == List.Empty<T>()) 的重要代码,则不同的对象可能会成为问题。另外,这种方法如何节省内存分配和垃圾回收呢? - Dan Tao
我的方法和你的相比可能不能减少垃圾,但它确实可以节省分配内存:每当你写 static readonly IList<T> EmptyList = new T[0] 时,你都会分配另一个 T[0] 对象。如果你使用 n 种不同类型的 T,则将分配 至少 nT[0] 对象,而我的方法将恰好分配 nT[0] 对象。顺便说一句,程序员永远不应该依赖于 list == List.Empty<T>()(或者 e == Enumerable.Empty<T>(),因为它们存在相同的问题)。毕竟,Empty<T>() 是一个方法,而方法通常不能保证总是返回相同的值。 - Daniel A.A. Pelsmaeker
1
好的,没错。你已经证明了直接访问静态字段比通过另一个类中的方法访问它要快两倍(在我的电脑上)。虽然这与延迟初始化无关,但你最初关于性能的论点是正确的。显然,过早地进行优化永远不应该成为争论的理由,但我开始对你的急切方法感到温暖了。我进行了一些测试,并得出结论,无论哪种方式,类型都不会在第一次需要时之前创建。你认为.NET框架开发人员为什么也选择了延迟加载的方法呢? - Daniel A.A. Pelsmaeker
显示剩余3条评论

-1

这个怎么样:

readonly List<T> mylist = new List<T>();

不确定为什么你想要它是只读的;在我能想到的大多数情况下,这并没有太多意义。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接