Пустой массив в .NET использует пространство? - PullRequest
24 голосов
/ 30 сентября 2008

У меня есть код, где я возвращаю массив объектов.

Вот упрощенный пример:

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]?
Должен ли я просто возвратить ноль и заставить вызывающего принять ноль в качестве действительного способа указания «ничего не найдено»?

Примечание: это вызывается в цикле, который запускается сотни и сотни раз, так что это один из немногих случаев, когда я думаю, что этот тип оптимизации не является «преждевременным».

PS: И даже если бы это было преждевременно, я все равно хотел бы знать, как это работает: -)

Обновление:

Первоначально, когда я спросил, использует ли он какой-либо пробел, я думал о вещах с точки зрения C / C ++, вроде как в C, написание char a[5]; выделит 5 байт пространства в стеке, и char b[0]; выделит 0 байтов.

Я понимаю, что это не очень подходит для мира .NET, но мне было любопытно, если бы это было что-то, что компилятор или CLR могли бы обнаружить и оптимизировать, так как массив с нулевым размером, не изменяемого размера, действительно не должен насколько я вижу?) требуется какое-то место для хранения.

Ответы [ 9 ]

56 голосов
/ 30 сентября 2008

Даже если бы его называли «сотни и сотни раз», я бы сказал, что это преждевременная оптимизация. Если результат более ясен как пустой массив, используйте его.

Теперь для фактического ответа: да, пустой массив занимает немного памяти. Он имеет обычные служебные данные (8 байт на x86, я считаю) и 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 generics делает это тривиальным:

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

(Вы, конечно, можете обернуть его в свойство.)

Тогда просто используйте: Массивы .Empty;

РЕДАКТИРОВАТЬ: Я только что вспомнил сообщение Эрика Липперта о массивах . Вы уверены, что массив является наиболее подходящим типом для возврата?

9 голосов
/ 22 мая 2015

Следующая версия 4.6 .NET (позднее в 2015 году) содержит статический метод , возвращающий нулевую длину string[]:

Array.Empty<string>()

Полагаю, он возвращает один и тот же экземпляр, если вызывается много раз.

5 голосов
/ 30 сентября 2008

Объявленные массивы всегда должны содержать следующую информацию:

  • Ранг (количество измерений)
  • Тип для содержания
  • Длина каждого измерения

Скорее всего, это будет тривиально, но при большем количестве измерений и большей длине будет оказывать влияние на производительность для циклов.

Что касается возвращаемых типов, я согласен, что вместо нуля должен быть возвращен пустой массив.

Больше информации здесь: Типы массивов в .NET

4 голосов
/ 30 сентября 2008

Да, как уже говорили другие, пустой массив занимает несколько байтов для заголовка объекта и поля длины.

Но если вы беспокоитесь о производительности, в этом методе вы сосредотачиваетесь не на той ветви выполнения. Я был бы гораздо больше обеспокоен вызовом ToArray в заполненном списке, который приведет к выделению памяти, равной его внутреннему размеру, и копии содержимого списка в нем.

Если вы действительно хотите улучшить производительность, то (если это возможно) верните список напрямую, сделав тип возвращаемого значения одним из: List<T>, IList<T>, ICollection<T>, IEnumerable<T> в зависимости от того, какие средства вам нужны из него (обратите внимание, что менее конкретное лучше в общем случае) .

3 голосов
/ 16 февраля 2009

Другие ответили на ваш вопрос приятно. Так что просто сделать замечание ...

Я бы не стал возвращать массив (если вы не можете). Придерживайтесь IEnumerable, и тогда вы сможете использовать Enumerable.Empty<T>() из API LINQ. Очевидно, что Microsoft оптимизировала этот сценарий для вас.

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

    return Enumerable.Empty<string>();
}
3 голосов
/ 30 сентября 2008

Я бы предположил, что пустой массив использует только пространство, необходимое для выделения самого указателя объекта.

Из памяти руководящие принципы API говорят, что вы всегда должны возвращать пустой массив из метода, который возвращает массив, а не возвращать ноль, поэтому я бы оставил ваш код таким, какой он есть. Таким образом, вызывающий объект знает, что он гарантированно получит массив (даже пустой), и ему не нужно проверять нулевое значение при каждом вызове.

Редактировать: ссылка о возврате пустых массивов:

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

2 голосов
/ 30 сентября 2008

Это не прямой ответ на ваш вопрос.

Прочитайте, почему массивы считаются несколько вредными . Я бы предложил вам вернуть IList в этом случае и немного реструктурировать код:

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);
0 голосов
/ 30 июля 2018

Я знаю, что это старый вопрос, но это основной вопрос, и мне нужен был подробный ответ.

Итак, я исследовал это и получил результаты:

В .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 против 50 06 8f 70)
  • Тогда он содержит размер массива в целочисленной переменной (little-endian). так что 00 00 00 00 для myArray и 01 00 00 00 для myArray2
  • Теперь это наши драгоценные данные [Я тестировал с Немедленное окно ]
  • После этого мы видим постоянную (00 00 00 00). Я не знаю, что это значит.

Теперь я чувствую себя намного лучше насчет массива нулевой длины, я знаю, как он работает =]

0 голосов
/ 30 сентября 2008

Если я правильно понимаю, для строковых массивов будет выделено небольшое количество памяти. Ваш код, по сути, требует создания общего списка, так почему бы просто не вернуть его?

[EDIT] Удалена версия кода, которая вернула нулевое значение. Другие ответы, рекомендующие использовать нулевые возвращаемые значения в этом случае, являются лучшим советом [/ EDIT]

List<string> GetTheStuff()
{
   List<string> s = new List<string();
   if (somePredicarte())
   {
      // more code
   }
   return s;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...