¿Cuál es la diferencia entre String.Empty y "" (cadena vacía)?

288

En .NET, ¿cuál es la diferencia entre String.Emptyy "", y son intercambiables, o hay alguna referencia subyacente o problemas de localización en torno a la igualdad que String.Emptygaranticen que no sea un problema?

johnc
fuente
2
La verdadera pregunta NO es qué, sino por qué . Por qué se le ocurrió a Microsoft string.Emptyy cuál fue la razón detrás de declararlo en readonlylugar de const.
user1451111

Respuestas:

294

En .NET anterior a la versión 2.0, ""crea un objeto mientras string.Emptyno crea ningún objeto ref , lo que hacestring.Empty más eficiente.

En la versión 2.0 y posterior de .NET, todas las apariciones se ""refieren al mismo literal de cadena, lo que significa que ""es equivalente a .Empty, pero aún no tan rápido como .Length == 0.

.Length == 0 es la opción más rápida, pero .Empty hace que el código sea un poco más limpio.

Consulte la especificación .NET para obtener más información .

Brian R. Bondy
fuente
9191
"" solo habría creado un objeto una vez de todos modos debido a la secuencia interna. Básicamente, la compensación del rendimiento es el maní: la legibilidad es más importante.
Jon Skeet el
12
Es interesante que a pesar de que la cuestión no dice nada sobre la comparación de una cadena a "" o String.Empty para comprobar si hay cadenas vacías, muchas personas parecen interpretar la pregunta de esa manera ...
peSHIr
11
Tendría cuidado con .Length == 0, ya que puede lanzar una excepción si su variable de cadena es nula. Sin embargo, si lo comprueba con "", solo devolverá correctamente false sin excepción.
Jeffrey Harmon
11
@JeffreyHarmon: O podrías usarlo string.IsNullOrEmpty( stringVar ).
Flynn1179
1
Si alguien quiere evitar las malas prácticas para probar una cadena vacía, puede habilitar CA1820 en Code Analysis. docs.microsoft.com/visualstudio/code-quality/…
sean
197

¿Cuál es la diferencia entre String.Empty y "", y son intercambiables?

string.Emptyes un campo de solo lectura, mientras que ""es una constante de tiempo de compilación. Los lugares donde se comportan de manera diferente son:

Valor de parámetro predeterminado en C # 4.0 o superior

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

Expresión de caso en la declaración de cambio

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

    case "":
        break;

}

Argumentos de atributo

[Example(String.Empty)]
// Error: An attribute argument must be a constant expression, typeof expression 
//        or array creation expression of an attribute parameter type
Habib
fuente
21
Interesante, no pensé que esta vieja pregunta obtendría información nueva relevante. Estaba equivocado
johnc
Creo que su valor de parámetro predeterminado del ejemplo n. ° 1 en C # 4.0 o superior es esencialmente un duplicado de sus argumentos de atributo del ejemplo n. ° 3 , ya que creo que .NET implementa los parámetros predeterminados en los atributos. Entonces, más básicamente, simplemente no puede poner un "valor" (tiempo de ejecución) (en este caso, un identificador de instancia , para los sticklers) en metadatos (tiempo de compilación).
Glenn Slayden
@GlennSlayden, no estoy de acuerdo contigo. La inicialización de atributos es diferente de una inicialización ordinaria. Esto se debe a que puede pasar el String.Emptyargumento como argumento en la mayoría de los casos. Tiene razón en que todos los ejemplos muestran eso you simply can't put a (run-time) "value" into (compile-time) metadatay eso es lo que los ejemplos pretenden mostrar.
Sasuke Uchiha
42

Las respuestas anteriores eran correctas para .NET 1.1 (mire la fecha de la publicación que vincularon: 2003). A partir de .NET 2.0 y versiones posteriores, esencialmente no hay diferencia. El JIT terminará haciendo referencia al mismo objeto en el montón de todos modos.

De acuerdo con la especificación C #, sección 2.4.4.5: http://msdn.microsoft.com/en-us/library/aa691090(VS.71).aspx

Cada cadena literal no necesariamente da como resultado una nueva instancia de cadena. Cuando dos o más literales de cadena que son equivalentes según el operador de igualdad de cadena (Sección 7.9.7) aparecen en el mismo ensamblaje, estos literales de cadena se refieren a la misma instancia de cadena.

Alguien incluso menciona esto en los comentarios de la publicación de Brad Abram

En resumen, el resultado práctico de "" vs. String.Empty es nulo. El JIT lo resolverá al final.

Personalmente, he descubierto que el JIT es mucho más inteligente que yo, por lo que trato de no ser demasiado inteligente con optimizaciones de microcompiladores como esa. El JIT se desplegará para bucles (), eliminará código redundante, métodos en línea, etc. mejor y en momentos más apropiados de lo que yo o el compilador de C # podríamos anticipar de antemano. Deje que el JIT haga su trabajo :)

chadmyers
fuente
1
Pequeño error tipográfico? "El JIT terminará haciendo referencia al mismo objeto en la ayuda de todos modos". ¿Quiso decir "en el montón"?
Dana
Pregunta popular relacionada stackoverflow.com/questions/263191/…
Michael Freidgeim
36

String.Emptyes un campo de solo lectura mientras que ""es una constante . Esto significa que no se puede usar String.Emptyen una declaración de cambio porque no es una constante.

James Newton-King
fuente
con el advenimiento de la defaultpalabra clave podemos promover la legibilidad sin, evitar modificaciones accidentales y tener una constante de tiempo de compilación, aunque para ser honesto, sigo pensando que String.Empty es más legible que el predeterminado, pero más lento de escribir
Enzoaeneas
18

Otra diferencia es que String.Empty genera un código CIL más grande. Si bien el código para hacer referencia a "" y String.Empty tiene la misma longitud, el compilador no optimiza la concatenación de cadenas (consulte la publicación del blog de Eric Lippert ) para ver los argumentos de String.Empty. Las siguientes funciones equivalentes

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

generar esta 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 
}
Bruno Martinez
fuente
Punto válido pero ¿quién haría tal cosa? ¿Por qué debería concatenar una cadena vacía a algo a propósito?
Robert S.
@RobertS. Tal vez la segunda cadena estaba en una función separada que se alineó. Es raro, te lo garantizo.
Bruno Martinez
3
no es tan raro usar el operador ternario:"bar " + (ok ? "" : "error")
simbionte
13

Las respuestas anteriores son técnicamente correctas, pero lo que realmente desea usar, para una mejor legibilidad del código y la menor posibilidad de una excepción es String.IsNullOrEmpty (s)

Eugene Katz
fuente
3
En términos de comparación de igualdad, estoy completamente de acuerdo, pero la pregunta también era sobre la diferencia entre los dos conceptos, así como la comparación
johnc
1
Tenga en cuenta que "la menor posibilidad de una excepción" a menudo significa "la mayor probabilidad de continuar pero de hacer algo mal". Por ejemplo, si tiene un argumento de línea de comando que está analizando y alguien llama a su aplicación, --foo=$BARentonces probablemente desee detectar la diferencia entre ellos olvidando establecer una variable de env y que no pasen la bandera en absoluto. string.IsNullOrEmptyA menudo es un olor a código que no ha validado sus entradas correctamente o está haciendo cosas raras. En general, no debe aceptar cadenas vacías cuando realmente quiere usar null, o algo así como un tipo Quizás / Opción.
Alastair Maw
11

Tiendo a usarlo en String.Emptylugar de hacerlo ""por una razón simple, pero no obvia: ""y ""NO son lo mismo, el primero tiene 16 caracteres de ancho cero. Obviamente, ningún desarrollador competente va a poner caracteres de ancho cero en su código, pero si llegan allí, puede ser una pesadilla de mantenimiento.

Notas:

  • Solía U + FEFF en este ejemplo.

  • No estoy seguro de si SO va a comer esos caracteres, pero pruébelo usted mismo con uno de los muchos caracteres de ancho cero

  • Solo encontré esto gracias a https://codegolf.stackexchange.com/

The_Lone_Devil
fuente
Esto merece más votos a favor. He visto este problema ocurrir a través de la integración del sistema a través de plataformas.
EvilDr
8

Usar en String.Emptylugar de "".

Esto es más por la velocidad que por el uso de la memoria, pero es un consejo útil. El ""es un literal, por lo que actuará como un literal: en el primer uso se crea y para los siguientes usos se devuelve su referencia. ¡Solo ""se almacenará una instancia en la memoria, sin importar cuántas veces la usemos! No veo ninguna penalización de memoria aquí. El problema es que cada vez que ""se usa, se ejecuta un ciclo de comparación para verificar si ""ya está en el grupo interno. Por otro lado, String.Empty hay una referencia a una zona de memoria"" almacenada en .NET Framework . String.Emptyapunta a la misma dirección de memoria para aplicaciones VB.NET y C #. Entonces, ¿por qué buscar una referencia cada vez que la necesita ?"" cuando tiene esa referencia enString.Empty

Referencia: String.Emptyvs""

Mojtaba Rezaeian
fuente
2
Esto no ha sido así desde .net 2.0
nelsontruran
6

String.Empty no crea un objeto mientras que "" sí lo hace. Sin embargo, la diferencia, como se señala aquí , es trivial.

Esteban Araya
fuente
44
No es trivial si marca una cadena para string.Empty o "". Uno realmente debería usar String.IsNullOrEmpty como Eugene Katz señaló. De lo contrario, obtendrá resultados inesperados.
Sigur
6

Todas las instancias de "" son el mismo literal de cadena interno (o deberían serlo). Por lo tanto, realmente no lanzará un nuevo objeto en el montón cada vez que use "", sino que simplemente cree una referencia al mismo objeto interno. Dicho esto, prefiero la cadena. Vacío. Creo que hace que el código sea más legible.

Jason Jackson
fuente
4
string mystring = "";
ldstr ""

ldstr empuja una nueva referencia de objeto a un literal de cadena almacenado en los metadatos.

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

ldsfld empuja el valor de un campo estático en la pila de evaluación

Tiendo a usar en String.Emptylugar de ""porque en mi humilde opinión es más claro y menos VB-ish.

Salvuccino
fuente
3

Eric Lippert escribió (17 de junio de 2013):
"El primer algoritmo en el que trabajé en el compilador de C # fue el optimizador que maneja las concatenaciones de cadenas. Desafortunadamente no logré portar estas optimizaciones al código base de Roslyn antes de irme; espero que alguien lo haga; llegar a eso! "

Aquí hay algunos resultados de Roslyn x64 a partir de enero de 2019. A pesar de las observaciones consensuadas de las otras respuestas en esta página, no me parece que el JIT x64 actual esté tratando todos estos casos de manera idéntica, cuando todo está dicho y hecho.

Sin embargo, String.Concattenga en cuenta, en particular, que solo uno de estos ejemplos realmente termina llamando , y supongo que eso es por razones de corrección oscuras (en lugar de una supervisión de optimización). Las otras diferencias parecen más difíciles de explicar.


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


Detalles de la prueba

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

Llegando a esto desde el punto de vista de Entity Framework: EF versiones 6.1.3 parece tratar String.Empty y "" de manera diferente al validar.

string.Empty se trata como un valor nulo para fines de validación y arrojará un error de validación si se usa en un campo Requerido (atribuido); donde como "" pasará la validación y no arrojará el error.

Este problema puede resolverse en EF 7+. Referencia: - https://github.com/aspnet/EntityFramework/issues/2610 ).

Editar: [Obligatorio (AllowEmptyStrings = true)] resolverá este problema, permitiendo que string.Empty se valide.

Antonio
fuente
2

Como String.Empty no es una constante de tiempo de compilación, no puede usarlo como valor predeterminado en la definición de funciones.

public void test(int i=0,string s="")
    {
      // Function Body
    }
tsiva124
fuente
1
Solo el resumen de esta respuesta: public void test(int i=0, string s=string.Empty) {}no se compilará y dirá "El valor del parámetro predeterminado para 's' debe ser una constante de tiempo de compilación. La respuesta de OP funciona.
bugybunny
1

Cuando escanea visualmente a través del código, "" aparece coloreado de la misma forma que las cadenas. string.Empty se parece a un acceso regular de miembro de clase. Durante una mirada rápida, es más fácil detectar "" o intuir el significado.

Encuentra las cadenas (la coloración de desbordamiento de pila no está ayudando exactamente, pero en VS esto es más obvio):

var i = 30;
var f = Math.Pi;
var s = "";
var d = 22.2m;
var t = "I am some text";
var e = string.Empty;
K0D4
fuente
2
Haces un buen punto, pero ¿el desarrollador realmente quiso decir ""? ¿Quizás querían poner un valor aún desconocido y olvidaron regresar? string.Empty tiene la ventaja de darle la confianza de que el autor original realmente quiso decir string.Empty. Es un punto menor lo sé.
mark_h
Además, un desarrollador malintencionado (o descuidado) puede poner un carácter de ancho cero entre las comillas en lugar de usar la secuencia de escape apropiada como \u00ad.
Palec
-8

Todos aquí dieron alguna buena aclaración teórica. Tuve una duda similar. Así que probé una codificación básica. Y encontré una diferencia. Aquí está la diferencia.

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


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

Entonces parece que "Nulo" significa absolutamente vacío y "Cadena. Vacío" significa que contiene algún tipo de valor, pero está vacío.

DeepakTheGeek
fuente
77
Tenga en cuenta que la pregunta es sobre ""versus string.Empty. Solo cuando se trataba de saber si la cadena está vacía, se nullhabía mencionado.
Palec