.NET中的空数组是否使用任何空间?

时间:2020-03-06 14:54:33  来源:igfitidea点击:

我有一些代码,我在其中返回一个对象数组。

这是一个简化的示例:

string[] GetTheStuff() {
    List<string> s = null;
    if( somePredicate() ) {
        s = new List<string>(); // imagine we load some data or something
    }
    return (s == null) ? 
        new string[0] :
        s.ToArray();
}

问题是,new string [0]的价格是多少?
我应该只返回null并让呼叫者接受null作为指示"未找到任何内容"的有效方法吗?

注意:这被称为循环运行,循环运行数百次,所以这是我认为这种优化实际上并非"过早"的少数情况之一。

PS:即使过早,我仍然想知道它是如何工作的:-)

更新:

最初,当我问它是否使用了任何空间时,我是从'C / C ++'的角度考虑问题的,就像在C语言中一样,编写char a [5];将在磁盘上分配5个字节的空间。堆栈,而char b [0];将分配0个字节。

我意识到这不是.NET世界的理想选择,但是我很好奇这是否是编译器或者CLR可以检测并优化的东西,因为大小为零的不可调整大小的数组实际上不应该(到目前为止)我可以看到吗?)需要任何存储空间。

解决方案

我猜想一个空数组仅使用分配对象指针本身所需的空间。

API准则从内存中说,我们应该始终从返回数组的方法而不是返回null的方法中返回空数组,因此无论如何我都会保留代码。这样,调用方就知道可以保证得到一个数组(甚至是一个空数组),并且不必在每次调用时都检查是否为null。

编辑:有关返回空数组的链接:

http://wesnerm.blogs.com/net_undocumented/2004/02/empty_arrays.html

声明的数组将始终必须包含以下信息:

  • 等级(维数)
  • 要包含的类型
  • 每个尺寸的长度

这很可能是微不足道的,但是对于更大数量的尺寸和更大的长度,它将对循环产生性能影响。

至于返回类型,我同意应该返回一个空数组而不是null。

这里的更多信息:.NET中的数组类型

如果我理解正确,则会为字符串数组分配少量内存。无论如何,代码本质上都需要创建一个通用列表,所以为什么不直接返回它呢?

[EDIT]删除了返回空值的代码版本。在这种情况下,建议针对空返回值的其他答案似乎是更好的建议[/ EDIT]

List<string> GetTheStuff()
{
   List<string> s = new List<string();
   if (somePredicarte())
   {
      // more code
   }
   return s;
}

即使它被称为"数百次",我也会说这是一个过早的优化。如果结果更清晰地显示为空数组,请使用该数组。

现在是实际答案:是的,一个空数组会占用一些内存。它具有正常的对象开销(我相信x86上为8字节)和4字节的计数。我不知道除此之外是否还有其他功能,但它并非完全免费。 (虽然价格便宜得令人难以置信...)

幸运的是,我们可以在不影响API本身的情况下进行优化:拥有一个"常量"的空数组。如果我们允许的话,我还做了另一个小的更改以使代码更清晰...

private static readonly string[] EmptyStringArray = new string[0];

string[] GetTheStuff() {
    if( somePredicate() ) {
        List<string> s = new List<string>(); 
        // imagine we load some data or something
        return s.ToArray();
    } else {
        return EmptyStringArray;
    }
}

如果发现自己经常需要此功能,则甚至可以创建一个带有静态成员的泛型类,以返回正确类型的空数组。 .NET泛型的工作方式很简单:

public static class Arrays<T> {
    public static readonly Empty = new T[0];
}

(当然,我们可以将其包装在属性中。)

然后只需使用:Arrays <string> .Empty;

编辑:我刚刚记得埃里克·利珀特(Eric Lippert)在阵列上的帖子。我们确定数组是最适合返回的类型吗?

这不是我们问题的直接答案。

阅读为什么数组被认为有些有害。在这种情况下,我建议我们返回一个IList <string>并重组一下代码:

IList<string> GetTheStuff() {
    List<string> s = new List<string>();
    if( somePredicate() ) {
        // imagine we load some data or something
    }
    return s;
}

这样,调用方就不必关心空的返回值。

编辑:如果返回的列表不可编辑,则可以将列表包装在ReadOnlyCollection中。只需将最后一行更改为。我也将考虑这种最佳做法。

return new ReadOnlyCollection(s);

是的,正如其他人所说的,空数组占用对象头和长度字段的几个字节。

但是,如果我们担心性能,那么我们将把重点放在此方法的错误执行分支上。我会更担心填充列表上的ToArray调用,这将导致与其内部大小相等的内存分配以及列表内容的内存副本进入其中。

如果我们确实想提高性能,那么(如果可能)通过将返回类型设为以下之一来直接返回列表:List <T>,IList <T>,ICollection <T>,IEnumerable <T>,具体取决于我们使用的工具是什么需要它(请注意,一般情况下,较少的特定性更好)。

其他人很好地回答了问题。因此,简单说明一下...

我将避免返回数组(除非我们不能这样做)。坚持使用IEnumerable,然后可以从LINQ API使用Enumerable.Empty &lt;T>()。显然,Microsoft已为我们优化了此方案。

IEnumerable<string> GetTheStuff()
{
    List<string> s = null;
    if (somePredicate())
    {
        var stuff = new List<string>();
        // load data
        return stuff;
    }

    return Enumerable.Empty<string>();
}