26

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

这是一个简化的示例:

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 会检测和优化出来的东西,因为大小为零的不可调整大小的数组真的不应该(就我可以看到?)需要任何存储空间。

4

9 回答 9

56

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

现在给出实际答案:是的,一个空数组需要一些内存。它具有正常的对象开销(我相信 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 在 arrays 上的帖子。您确定数组是最适合返回的类型吗?

于 2008-09-30T06:16:23.193 回答
9

即将发布的 .NET 4.6 版(2015 年晚些时候)包含一个返回长度为零的静态方法string[]

Array.Empty<string>()

我想如果多次调用它会返回相同的实例。

于 2015-05-22T20:09:40.203 回答
5

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

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

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

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

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

于 2008-09-30T06:13:23.137 回答
4

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

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

如果您真的想提高性能,那么(如果可能)通过将返回类型设置为以下之一直接返回列表:List<T>, IList<T>, ICollection<T>, IEnumerable<T>取决于您需要的工具(请注意,在一般情况下,不太具体更好)。

于 2008-09-30T07:53:45.347 回答
3

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

从内存中,API 指南说您应该始终从返回数组而不是返回 null 的方法返回一个空数组,所以无论如何我都会让您的代码保持原样。这样,调用者就知道他一定会得到一个数组(甚至是一个空数组),并且不需要在每次调用时检查 null。

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

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

于 2008-09-30T06:10:35.873 回答
3

其他人很好地回答了你的问题。所以只是一个简单的点...

我会避免返回一个数组(除非你不能)。坚持使用 IEnumerable,然后您可以Enumerable.Empty<T>()从 LINQ API 中使用。显然微软已经为你优化了这个场景。

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

    return Enumerable.Empty<string>();
}
于 2009-02-16T17:54:32.097 回答
2

这不是您问题的直接答案。

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

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

通过这种方式,调用者不必关心空返回值。


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

    return new ReadOnlyCollection(s);
于 2008-09-30T06:18:32.423 回答
2

我知道这是一个老问题,但这是一个基本问题,我需要一个详细的答案。

所以我对此进行了探索并得到了结果:

在 .Net 中,当您创建一个数组(在此示例中我使用int[])时,您需要6 个字节,然后再为您的数据分配任何内存。

考虑以下代码 [在32 位应用程序中!]:

int[] myArray = new int[0];
int[] myArray2 = new int[1];
char[] myArray3 = new char[0];

并查看内存:

myArray:  a8 1a 8f 70 00 00 00 00 00 00 00 00
myArray2: a8 1a 8f 70 01 00 00 00 00 00 00 00 00 00 00 00
myArray3: 50 06 8f 70 00 00 00 00 00 00 00 00

让我们解释一下内存:

  • 看起来前 2 个字节是某种元数据,您可以看到它在int[]char[] ( a8 1a 8f 70 vs 50 06 8f 70)之间变化
  • 然后它将数组的大小保存在整数变量(小端)中。所以它是00 00 00 00为了myArray01 00 00 00为了myArray2
  • 现在它是我们宝贵的数据[我用即时窗口测试过]
  • 之后我们看到一个常数 ( 00 00 00 00)。我不知道它是什么意思。

现在我对零长度数组感觉好多了,我知道它是如何工作的 =]

于 2018-07-30T12:13:44.603 回答
0

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

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

List<string> GetTheStuff()
{
   List<string> s = new List<string();
   if (somePredicarte())
   {
      // more code
   }
   return s;
}
于 2008-09-30T06:15:43.663 回答