В чем разница между String.Empty и «» (пустая строка)?


288

В .NET, в чем разница между String.Emptyи "", и являются ли они взаимозаменяемыми, или есть какие-то основные ссылки или проблемы локализации вокруг равенства, String.Emptyкоторые гарантируют, что это не проблема?


2
Реальный вопрос заключается не в чем , а почему . Почему Microsoft придумала string.Emptyи каково было основание объявить это readonlyвместо const.
user1451111

Ответы:


295

В .NET до версии 2.0 ""создает объект, а string.Emptyобъект ref не создает , что делает его string.Emptyболее эффективным.

В версии 2.0 и более поздних версиях .NET все вхождения ""ссылаются на один и тот же строковый литерал, что означает ""эквивалент .Empty, но все же не так быстро, как .Length == 0.

.Length == 0это самый быстрый вариант, но .Emptyделает код немного чище.

См. .NET спецификацию для получения дополнительной информации .


91
"" в любом случае только один раз создал бы объект из-за интернирования строк. В основном компромисс между производительностью и арахисом - удобочитаемость важнее.
Джон Скит

12
Интересно, что хотя в этом вопросе ничего не говорится о сравнении строки с "" или строкой. Не удается проверить наличие пустых строк, многие люди, похоже, интерпретируют вопрос таким образом ...
peSHIr

11
Я был бы осторожен с .Length == 0, так как он может выдать исключение, если ваша строковая переменная равна нулю. Если вы проверите его в "", он просто вернет false без исключения.
Джеффри Хармон,

11
@JeffreyHarmon: Или ты мог бы использовать string.IsNullOrEmpty( stringVar ).
Flynn1179

1
Если кто-то хочет предотвратить плохие практики для проверки пустой строки, вы можете включить CA1820 в Анализ кода. docs.microsoft.com/visualstudio/code-quality/…
Шон

197

в чем разница между String.Empty и "", и являются ли они взаимозаменяемыми

string.Emptyявляется полем только для чтения, тогда как ""является постоянной времени компиляции. Места, где они ведут себя по-разному:

Значение параметра по умолчанию в C # 4.0 или выше

void SomeMethod(int ID, string value = string.Empty)
// Error: Default parameter value for 'value' must be a compile-time constant
{
    //... implementation
}

Выражение регистра в операторе switch

string str = "";
switch(str)
{
    case string.Empty: // Error: A constant value is expected. 
        break;

    case "":
        break;

}

Аргументы атрибута

[Example(String.Empty)]
// Error: An attribute argument must be a constant expression, typeof expression 
//        or array creation expression of an attribute parameter type

21
Интересно, я не думал, что этот старый вопрос получит какую-либо соответствующую новую информацию. Я был неправ
Джон

Я думаю, что ваш пример # 1 Значение параметра по умолчанию в C # 4.0 или выше по сути является дубликатом ваших аргументов атрибута # 3 примера, поскольку я считаю, что .NET развертывает параметры по умолчанию в атрибуты. Таким образом, в сущности, вы просто не можете поместить (во время выполнения) «значение» (в данном случае дескриптор экземплярадля сторонников) в метаданные (во время компиляции).
Гленн

@GlennSlayden, я не согласен с тобой. Инициализация атрибута отличается от обычной инициализации. Это потому, что вы можете передать в String.Emptyкачестве аргумента в большинстве случаев. Вы правы, что все примеры показывают это, you simply can't put a (run-time) "value" into (compile-time) metadataи это то, что примеры стремятся показать.
Саске Учиха

42

Предыдущие ответы были правильными для .NET 1.1 (посмотрите дату поста, на который они ссылались: 2003). Начиная с .NET 2.0 и более поздних версий, по сути, нет никакой разницы. JIT все равно будет ссылаться на один и тот же объект в куче.

Согласно спецификации C #, раздел 2.4.4.5: http://msdn.microsoft.com/en-us/library/aa691090(VS.71).aspx

Каждый строковый литерал не обязательно приводит к новому экземпляру строки. Когда два или более строковых литерала, эквивалентных по оператору равенства строк (раздел 7.9.7), появляются в одной сборке, эти строковые литералы ссылаются на один и тот же экземпляр строки.

Кто-то даже упоминает об этом в комментариях к посту Брэда Абрама

Таким образом, практический результат "" против String.Empty равен нулю. JIT выяснит это в конце.

Лично я обнаружил, что JIT намного умнее меня, и поэтому я стараюсь не слишком умничать с оптимизацией микрокомпилятора, подобным этому. JIT развернется для циклов (), удалит избыточный код, встроенные методы и т. Д. Лучше и в более подходящее время, чем когда-либо мог предвидеть компилятор I или C #. Пусть JIT сделает свое дело :)


1
Маленькая опечатка? «В любом случае JIT будет ссылаться на тот же объект в справке». Вы имели в виду "в куче"?
Дана

Связанный популярный вопрос stackoverflow.com/questions/263191/…
Майкл Фрейдгейм

36

String.Emptyполе только для чтения, в то время ""как const . Это означает, что вы не можете использовать String.Emptyв операторе switch, потому что это не константа.


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

18

Другое отличие состоит в том, что String.Empty генерирует больший код CIL. Хотя код для ссылки "" и String.Empty имеет одинаковую длину, компилятор не оптимизирует конкатенацию строк (см. Сообщение в блоге Эрика Липперта ) для аргументов String.Empty. Следующие эквивалентные функции

string foo()
{
    return "foo" + "";
}
string bar()
{
    return "bar" + string.Empty;
}

генерировать этот IL

.method private hidebysig instance string foo() cil managed
{
    .maxstack 8
    L_0000: ldstr "foo"
    L_0005: ret 
}
.method private hidebysig instance string bar() cil managed
{
    .maxstack 8
    L_0000: ldstr "bar"
    L_0005: ldsfld string [mscorlib]System.String::Empty
    L_000a: call string [mscorlib]System.String::Concat(string, string)
    L_000f: ret 
}

Допустимый момент, но кто будет делать такие вещи? Почему я должен специально объединить пустую строку с чем-то?
Роберт С.

@RobertS. Может быть, вторая строка была в отдельной функции, которая была встроена. Это редко, я даю вам.
Бруно Мартинес

3
это не так уж редко, используя троичный оператор:"bar " + (ok ? "" : "error")
symbiont

13

Приведенные выше ответы являются технически правильными, но то, что вы действительно хотите использовать для лучшей читабельности кода и наименьшей вероятности исключения, - это String.IsNullOrEmpty (s)


3
Что касается сравнения равенства, я полностью согласен, но вопрос был также о разнице между
этими

1
Остерегайтесь того, что «наименьший шанс исключения» часто означает «наибольший шанс продолжить, но сделать что-то не так». Например, если у вас есть аргумент командной строки, который вы анализируете, и кто-то вызывает ваше приложение, --foo=$BARвы, вероятно, захотите заметить разницу между ними, забывшими установить env var, и с тем, чтобы они вообще не передавали флаг. string.IsNullOrEmptyЭто часто является запахом кода, который вы не проверили правильно, или делаете странные вещи. Вы не должны обычно принимать пустые строки, когда вы действительно хотите их использовать null, или что-то вроде типа Maybe / Option.
Аластер Мо

11

Я склонен использовать, String.Emptyа не ""по одной простой, но не очевидной причине: ""и ""это НЕ одно и то же, первый фактически содержит 16 символов нулевой ширины. Очевидно, что ни один компетентный разработчик не собирается добавлять символы нулевой ширины в свой код, но если они туда попадут, это может стать кошмаром обслуживания.

Ноты:

  • Я использовал U + FEFF в этом примере.

  • Не уверен, что SO будет есть эти символы, но попробуйте сами с одним из множества символов нулевой ширины.

  • Я только столкнулся с этим благодаря https://codegolf.stackexchange.com/


Это заслуживает большего количества голосов. Я видел эту проблему через системную интеграцию на разных платформах.
EvilDr

8

Используйте, String.Emptyа не "".

Это больше для скорости, чем использование памяти, но это полезный совет. ""Является буквальным , так будет действовать как буквальным: на первом использовании оно создается и для следующих целей его ссылка возвращается. Только один экземпляр ""будет сохранен в памяти независимо от того, сколько раз мы его используем! Я не вижу здесь никаких штрафов за память. Проблема заключается в том, что каждый раз, когда ""используется, выполняется цикл сравнения, чтобы проверить, есть ли ""уже в пуле интернирования. С другой стороны, String.Empty это ссылка на ""хранящуюся в зоне памяти .NET Framework . String.Emptyуказывает на один и тот же адрес памяти для приложений VB.NET и C #. Так зачем искать ссылку каждый раз, "" когда вам нужно, когда у вас есть эта ссылка вString.Empty?

Ссылка: String.Emptyпротив""


2
Это не было правдой с тех пор .net 2.0
nelsontruran

6

String.Empty не создает объект, тогда как "" создает. Однако разница, как указано здесь , тривиальна.


4
Это не тривиально, если вы проверяете строку на string.Empty или "". Надо действительно использовать String.IsNullOrEmpty, как указал Юджин Кац. В противном случае вы получите неожиданные результаты.
Сигур

6

Все экземпляры "" являются одинаковыми интернированными строковыми литералами (или они должны быть). Таким образом, вы действительно не будете бросать новый объект в кучу каждый раз, когда используете «», а просто создаете ссылку на тот же самый интернированный объект. Сказав это, я предпочитаю строку. Пусто. Я думаю, что это делает код более читабельным.



4
string mystring = "";
ldstr ""

ldstr выдвигает новую ссылку на объект на строковый литерал, хранящийся в метаданных.

string mystring = String.Empty;
ldsfld string [mscorlib]System.String::Empty

ldsfld помещает значение статического поля в стек оценки

Я склонен использовать String.Emptyвместо, ""потому что ИМХО это более понятно и меньше VB-иш.


3

Эрик Липперт писал (17 июня 2013 г.):
«Первым алгоритмом, над которым я работал в компиляторе C #, был оптимизатор, который обрабатывает конкатенации строк. К сожалению, мне не удалось перенести эти оптимизации в кодовую базу Roslyn до того, как я ушел; добраться до этого! "

Вот некоторые результаты Roslyn x64 по состоянию на январь 2019 года. Несмотря на консенсусные замечания других ответов на этой странице, мне не кажется, что текущий JIT x64 рассматривает все эти случаи одинаково, когда все сказано и сделано.

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


default (String) + {default (String), "", String.Empty}

static String s00() => default(String) + default(String);
    mov  rax,[String::Empty]
    mov  rax,qword ptr [rax]
    add  rsp,28h
    ret

static String s01() => default(String) + "";
    mov  rax,[String::Empty]
    mov  rax,qword ptr [rax]
    add  rsp,28h
    ret

static String s02() => default(String) + String.Empty;
    mov  rax,[String::Empty]
    mov  rax,qword ptr [rax]
    mov  rdx,rax
    test rdx,rdx
    jne  _L
    mov  rdx,rax
_L: mov  rax,rdx
    add  rsp,28h
    ret

"" + {default (String), "", String.Empty}

static String s03() => "" + default(String);
    mov  rax,[String::Empty]
    mov  rax,qword ptr [rax]
    add  rsp,28h
    ret

static String s04() => "" + "";
    mov  rax,[String::Empty]
    mov  rax,qword ptr [rax]
    add  rsp,28h
    ret

static String s05() => "" + String.Empty;
    mov  rax,[String::Empty]
    mov  rax,qword ptr [rax]
    mov  rdx,rax
    test rdx,rdx
    jne  _L
    mov  rdx,rax
_L: mov  rax,rdx
    add  rsp,28h
    ret

String.Empty + {default (String), "", String.Empty}

static String s06() => String.Empty + default(String);
    mov  rax,[String::Empty]
    mov  rax,qword ptr [rax]
    mov  rdx,rax
    test rdx,rdx
    jne  _L
    mov  rdx,rax
_L: mov  rax,rdx
    add  rsp,28h
    ret

static String s07() => String.Empty + "";
    mov  rax,[String::Empty]
    mov  rax,qword ptr [rax]
    mov  rdx,rax
    test rdx,rdx
    jne  _L
    mov  rdx,rax
_L: mov  rax,rdx
    add  rsp,28h
    ret

static String s08() => String.Empty + String.Empty;
    mov  rcx,[String::Empty]
    mov  rcx,qword ptr [rcx]
    mov  qword ptr [rsp+20h],rcx
    mov  rcx,qword ptr [rsp+20h]
    mov  rdx,qword ptr [rsp+20h]
    call F330CF60                 ; <-- String.Concat
    nop
    add  rsp,28h
    ret


Детали теста

Microsoft (R) Visual C# Compiler version 2.10.0.0 (b9fb1610)
AMD64 Release
[MethodImpl(MethodImplOptions.NoInlining)]
'SuppressJitOptimization' = false

2

Исходя из этого, с точки зрения Entity Framework: версии EF 6.1.3, по-видимому, обрабатывают String.Empty и "" по-разному при проверке.

string.Empty обрабатывается как нулевое значение для целей проверки и выдает ошибку проверки, если она используется в поле Обязательное (приписывается); где как "" пройдет валидацию и не выдаст ошибку.

Эта проблема может быть решена в EF 7+. Ссылка: - https://github.com/aspnet/EntityFramework/issues/2610 ).

Редактировать: [Обязательный (AllowEmptyStrings = true)] решит эту проблему, позволив string.Empty для проверки.


2

Поскольку String.Empty не является константой времени компиляции, вы не можете использовать ее в качестве значения по умолчанию в определении функции.

public void test(int i=0,string s="")
    {
      // Function Body
    }

1
Просто резюме из этого ответа: public void test(int i=0, string s=string.Empty) {}не скомпилирует и скажет "Значение параметра по умолчанию для 's' должно быть константой времени компиляции. Ответ OP работает.
bugybunny

1

Когда вы визуально сканируете код, "" выглядит раскрашенным так, как раскрашиваются строки. string.Empty выглядит как обычный доступ к членам класса. Во время быстрого просмотра легче определить "" или интуитивно понять значение.

Определите строки (колоризация переполнения стека не совсем помогает, но в VS это более очевидно):

var i = 30;
var f = Math.Pi;
var s = "";
var d = 22.2m;
var t = "I am some text";
var e = string.Empty;

2
Вы сделали хорошее замечание, но действительно ли разработчик имел в виду ""? Возможно, они намеревались ввести какое-то еще неизвестное значение и забыли вернуться? Преимущество string.Empty заключается в том, что вы можете быть уверены, что первоначальный автор действительно имел в виду string.Empty. Это незначительный момент, я знаю.
mark_h

Кроме того, злонамеренный (или неосторожный) разработчик может поместить символ нулевой ширины между кавычками вместо использования соответствующей escape-последовательности, например \u00ad.
Палек

-8

Все здесь дали некоторые хорошие теоретические разъяснения. У меня было подобное сомнение. Поэтому я попробовал базовую кодировку на нем. И я нашел разницу. Вот в чем разница.

string str=null;
Console.WriteLine(str.Length);  // Exception(NullRefernceException) for pointing to null reference. 


string str = string.Empty;
Console.WriteLine(str.Length);  // 0

Таким образом, кажется, что «Null» означает абсолютно void, а «String.Empty» означает, что оно содержит какое-то значение, но оно пустое.


7
Обратите внимание, что вопрос о ""против string.Empty. Только при попытке узнать, пуста ли строка, nullбыло упомянуто.
Палек
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.