Почему CLR повторно использует пустые строки, но не пустые массивы?
Я заметил, что
Console.WriteLine((object) new string(' ', 0) == (object) new string(' ', 0));
выводит true
, что указывает на то, что среда CLR сохраняет пустую строку и повторно использует тот же экземпляр. (Он печатает false
для любого другого числа, кроме 0
.)
Однако для массивов это неверно:
Console.WriteLine(new int[0] == new int[0]); // False
Теперь, если мы посмотрим на реализацию Enumerable.Empty<T>()
, мы обнаружим, что он кэширует и повторно использует пустые массивы:
public static IEnumerable<TResult> Empty<TResult>()
{
return EmptyEnumerable<TResult>.Instance;
}
[...]
public static IEnumerable<TElement> Instance
{
get
{
if (EmptyEnumerable<TElement>.instance == null)
EmptyEnumerable<TElement>.instance = new TElement[0];
return EmptyEnumerable<TElement>.instance;
}
}
Таким образом, команда разработчиков почувствовала, что сохранение пустого массива для каждого типа стоит того. CLR мог бы, если бы захотел, сделать небольшой шаг дальше и сделать это изначально, поэтому он применяется не только к вызовам Enumerable.Empty<T>()
, но также и к new T[0]
. Если оптимизация в Enumerable.Empty<T>()
стоит того, наверняка, это было бы еще лучше?
Почему CLR не делает этого? Есть что-то, чего я не вижу?
Ответы
Ответ 1
Строки могут использовать интернирование, что делает их отличной историей (от всех других объектов).
Массивы по существу являются объектами. Повторное использование экземпляров, когда это не ясно из синтаксиса или контекста, не имеет побочных эффектов или рисков.
static int[] empty = new int[0];
...
lock (empty) { ... }
Если какой-то другой код заблокирован на другом (как они думали) пустом int[]
, у вас может быть тупик, который очень трудно найти.
Другие сценарии включают использование массивов в качестве ключа в словаре или где-либо еще их идентификация. Рамки не могут просто менять правила.
Ответ 2
Создание объекта с "новым" всегда создаст новый экземпляр, который может быть заблокирован отчетливо от любого другого экземпляра и который ReferenceEquals будет отчитываться как отличный от всех других экземпляров. Если для создания пустых массивов были созданы системные методы или свойства factory, похожие на Enumerable <T> .Empty или String.Empty, эти свойства могли бы возвращать экземпляры общих объектов, но открытые конструкторы не могут делать ничего, кроме возврата нового экземпляра или выбросить исключение.