Яка різниця між String.Empty та “” (порожній рядок)?


288

В .NET, в чому різниця між String.Emptyі "", і вони взаємозамінні, або є якась - то основна посилання або питання локалізації навколо рівності , які String.Emptyзабезпечать не проблема?


2
Справжнє питання НЕ що, а чому . Чому Microsoft придумала string.Emptyі що було обґрунтуванням декларування цього як readonlyзамість const.
user1451111

Відповіді:


295

У .NET до версії 2.0 ""створює об'єкт, при цьому string.Emptyне створює реферу об'єкта , що робить string.Emptyбільш ефективним.

У версії 2.0 і більш пізньої .NET все входження ""відносяться до однієї і тієї ж рядки литерала, що означає , що ""еквівалентно .Empty, але все ж не так швидко , як .Length == 0.

.Length == 0є найшвидшим варіантом, але .Emptyробить трохи більш чистим код.

Для отримання додаткової інформації див . Специфікацію .NET


91
"" об'єкт створив би об'єкт лише один раз через строкове інтернування. В основному компроміс ефективності - це арахіс - читабельність важливіша.
Джон Скіт

12
Цікаво, що, хоч питання не говорить про порівняння рядка з "" або "string". Порожньо, щоб перевірити, чи немає порожніх рядків, багато людей, здається, трактують питання таким чином ...
peSHIr

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

11
@JeffreyHarmon: Або ти можеш використовувати string.IsNullOrEmpty( stringVar ).
Flynn1179

1
Якщо хтось хоче запобігти недобросовісному використанню тесту на порожню рядок, можна включити CA1820 у кодовому аналізі. docs.microsoft.com/visualstudio/code-quality/…
sean

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
Цікаво, що я не думав, що це старе запитання отримає будь-яку відповідну нову інформацію. Я помилявся
johnc

Я думаю, що ваш приклад №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 розгорнуться для () циклів, видалить надлишковий код, вбудовані методи тощо і краще, і в більш підходящий час, ніж будь-який я або компілятор C # коли-небудь міг передбачити до початку. Нехай СІТ виконує свою роботу :)


1
Маленький друкарський помилок? "JIT в будь-якому випадку посилається на той самий об'єкт у довідці." Ви мали на увазі "на купі"?
Дана

Відносяться популярне питання stackoverflow.com/questions/263191 / ...
Michael Freidgeim

36

String.Emptyє полем для читання лише в той час "", як const . Це означає, що ви не можете використовувати String.Emptyв операторі перемикання, оскільки це не константа.


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

18

Ще одна відмінність полягає в тому, що String.Empty генерує більший код CIL. Хоча код для посилань на "" і String.Empty має однакову довжину, компілятор не оптимізує об'єднання рядків (див. Допис у блозі Еріка Ліпперта ) для аргументів String.Empty. Наступні еквівалентні функції

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

генерують цю ІЛ

.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")
симбіонт

13

Наведені вище відповіді технічно правильні, але те, що ви, можливо, хочете використовувати, для кращої читабельності коду та найменшого шансу на виняток - це String.IsNullOrEmpty (s)


3
Що стосується порівняння рівності, я повністю згоден, але питання
стосувалося

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

11

Я, як правило, використовую String.Emptyне ""одну просту, але не очевидну причину: ""і ""НЕ є однаковою, перша - насправді 16 символів нульової ширини. Очевидно, жоден грамотний розробник не збирається вводити символи нульової ширини у свій код, але якщо вони все-таки потраплять туди, це може бути кошмаром технічного обслуговування.

Примітки:

  • Я використовував U + FEFF в цьому прикладі.

  • Не впевнений, чи ТАК їсть цих персонажів, але спробуйте сам із одним із багатьох символів нульової ширини

  • Я натрапив на це лише завдяки https://codegolf.stackexchange.com/


Це заслуговує більшої кількості результатів. Я бачив, як ця проблема виникає через системну інтеграцію на різних платформах.
EvilDr

8

Використовуйте, String.Emptyа не "".

Це швидше, ніж швидкість використання пам'яті, але це корисна порада. Літерал ""є таким чином, він буде діяти як буквальний: при першому використанні він створюється, а для наступних застосувань повертається його посилання. ""У пам'яті буде збережено лише один екземпляр незалежно від того, скільки разів ми ним користуємося! Я не бачу жодних штрафних санкцій за пам'ять. Проблема полягає в тому, що кожного разу, коли ""використовується цикл, виконується порівняльний цикл, щоб перевірити, чи ""вже він знаходиться у пулі стажування. З іншого боку String.Empty - це посилання на ""збережену в зоні пам'яті .NET Framework . String.Emptyвказує на ту саму адресу пам'яті для програм VB.NET та C #. То навіщо шукати посилання кожного разу, коли вам потрібно ?"" коли вам коли у вас є посиланняString.Empty

Довідка: String.Emptyvs""


2
Це неправда з .net 2.0
nelsontruran

6

String.Empty не створює об'єкт, тоді як "" так. Однак, як зазначено тут , різниця банальна.


4
Це не тривіально, якщо ви перевіряєте рядок для string.Empty або "". Справді слід використовувати String.IsNullOrEmpty, як вказував Євген Кац. Інакше ви отримаєте несподівані результати.
Сигур

6

Усі екземпляри "" є однаковими, інтернованими рядковими буквами (або вони повинні бути). Таким чином, ви дійсно не будете кидати новий об’єкт на купу щоразу, коли використовуєте "", а просто створюєте посилання на той самий інтернований об'єкт. Сказавши це, я віддаю перевагу string.Empty. Я думаю, що це робить код читабельнішим.



4
string mystring = "";
ldstr ""

ldstr висуває нове посилання на об'єкт на літеральний рядок, що зберігається в метаданих.

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

ldsfld висуває значення статичного поля на стек оцінки

Я, як правило, використовую String.Emptyзамість того, ""що IMHO зрозуміліший і менш VB-ish.


3

Ерік Ліпперт писав (17 червня 2013 р.):
"Першим алгоритмом, над яким я працював у компіляторі C #, був оптимізатор, який обробляє рядкові конкатекації. На жаль, мені не вдалося перенести ці оптимізації до кодової бази Рослін; сподіваюся, хтось діставайся до цього! "

Ось деякі результати Roslyn x64 станом на січень 2019 року. Незважаючи на консенсусні зауваження інших відповідей на цій сторінці, мені не здається, що поточний x64 JIT трактує всі ці випадки однаково, коли все сказано і зроблено.

Однак, зауважте, що лише один із цих прикладів насправді закінчує дзвінки 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

"" + {за замовчуванням (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 + {за замовчуванням (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 та "" по-різному під час перевірки.

рядок.Empty вважається нульовим значенням для цілей перевірки, і він видасть помилку перевірки, якщо він буде використаний у полі Потрібне (атрибутивне); де як "" пройде перевірку і не видасть помилку.

Ця проблема може бути вирішена в EF 7+. Довідка: - https://github.com/aspnet/EntityFramework/isissue/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" повинно бути константою часу компіляції. Відповідь ОП працює.
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

Крім того, зловмисний (або необережний) розробник може ввести лапки нульової ширини між лапок, замість того, щоб використовувати відповідну послідовність втечі, як-от \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" означає абсолютно недійсне, а "String.Empty" означає, що воно містить якесь значення, але воно порожнє.


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