c# - truco - como saber si es plata con limon
¿Cuál es la forma más eficiente de determinar si una cadena no recortada está vacía en C#? (8)
Tengo una cadena que puede tener espacios en blanco alrededor y quiero verificar si está esencialmente vacía.
Hay varias maneras de hacer esto:
1 if (myString.Trim().Length == 0)
2 if (myString.Trim() == "")
3 if (myString.Trim().Equals(""))
4 if (myString.Trim() == String.Empty)
5 if (myString.Trim().Equals(String.Empty))
Soy consciente de que este suele ser un caso claro de optimización prematura, pero tengo curiosidad y hay una posibilidad de que esto se haga lo suficiente como para tener un impacto en el rendimiento.
Entonces, ¿cuál de estos es el método más eficiente?
¿Hay algún método mejor en el que no haya pensado?
Editar: notas para los visitantes a esta pregunta:
Ha habido algunas investigaciones increíblemente detalladas sobre esta cuestión, especialmente de Andy y Jon Skeet.
Si te has topado con la pregunta mientras buscas algo, bien vale la pena leer al menos las publicaciones de Andy y Jon en su totalidad.
Parece que hay unos pocos métodos muy eficientes y el más eficiente depende del contenido de las cadenas con las que tengo que lidiar.
Si no puedo predecir las cadenas (que no puedo en mi caso), los métodos IsEmptyOrWhiteSpace
de Jon parecen ser más rápidos en general.
Gracias a todos por su aporte. Voy a seleccionar la respuesta de Andy como la "correcta" simplemente porque merece el impulso de la reputación por el esfuerzo que hizo y Jon ya tiene una reputación de mil setecientos millones.
(EDITAR: Ver la parte inferior de la publicación para los puntos de referencia sobre diferentes micro-optimizaciones del método)
No lo recortes, ya que podría crear una nueva cuerda que en realidad no necesitas. En su lugar, busque en la cadena los caracteres que no sean espacios en blanco (para la definición que desee). Por ejemplo:
public static bool IsEmptyOrWhitespace(string text)
{
// Avoid creating iterator for trivial case
if (text.Length == 0)
{
return true;
}
foreach (char c in text)
{
// Could use Char.IsWhiteSpace(c) instead
if (c=='' '' || c==''/t'' || c==''/r'' || c==''/n'')
{
continue;
}
return false;
}
return true;
}
También puede considerar lo que quiere que haga el método si el text
es null
.
Posibles micro optimizaciones para experimentar con:
¿Es
foreach
más rápido o más lento que usar un buclefor
como el de abajo? Tenga en cuenta que con el ciclofor
puede eliminar la prueba "if (text.Length==0)
" al inicio.for (int i = 0; i < text.Length; i++) { char c = text[i]; // ...
Lo mismo que arriba, pero alzando la llamada de
Length
. Tenga en cuenta que esto no es bueno para las matrices normales, pero podría ser útil para las cadenas. No lo he probado.int length = text.Length; for (int i = 0; i < length; i++) { char c = text[i];
En el cuerpo del ciclo, ¿hay alguna diferencia (en velocidad) entre lo que tenemos y:
if (c != '' '' && c != ''/t'' && c != ''/r'' && c != ''/n'') { return false; }
¿Un interruptor / caja sería más rápido?
switch (c) { case '' '': case ''/r'': case ''/n'': case ''/t'': return false; }
Actualización sobre el comportamiento Trim
Acabo de ver cómo Trim
puede ser tan eficiente como esto. Parece que Trim
solo creará una nueva cadena si es necesario. Si puede devolver this
o ""
:
using System;
class Test
{
static void Main()
{
CheckTrim(string.Copy(""));
CheckTrim(" ");
CheckTrim(" x ");
CheckTrim("xx");
}
static void CheckTrim(string text)
{
string trimmed = text.Trim();
Console.WriteLine ("Text: ''{0}''", text);
Console.WriteLine ("Trimmed ref == text? {0}",
object.ReferenceEquals(text, trimmed));
Console.WriteLine ("Trimmed ref == /"/"? {0}",
object.ReferenceEquals("", trimmed));
Console.WriteLine();
}
}
Esto significa que es muy importante que cualquier punto de referencia en esta pregunta utilice una combinación de datos:
- Cuerda vacía
- Espacio en blanco
- Espacio en blanco alrededor del texto
- Texto sin espacio en blanco
Por supuesto, el equilibrio del "mundo real" entre estos cuatro es imposible de predecir ...
Puntos de referencia He realizado algunos puntos de referencia de las sugerencias originales en comparación con las mías, y la mía parece ganar en todo lo que le lanzo, lo que me sorprende dados los resultados en otras respuestas. Sin embargo, también comparé la diferencia entre foreach
, for
usar text.Length
, for
usar text.Length
una vez y luego invirtiendo el orden de iteración, y for
con una longitud alzada.
Básicamente, el bucle for
es muy ligeramente más rápido, pero al levantar la verificación de longitud es más lento que foreach
. Invertir la dirección de bucle for
es muy levemente más lento que foreach
también. Sospecho fuertemente que el JIT está haciendo cosas interesantes aquí, en términos de eliminar cheques de límites duplicados, etc.
Código: (ver mi entrada de blog de benchmarking para el marco en el que está escrito)
using System;
using BenchmarkHelper;
public class TrimStrings
{
static void Main()
{
Test("");
Test(" ");
Test(" x ");
Test("x");
Test(new string(''x'', 1000));
Test(" " + new string(''x'', 1000) + " ");
Test(new string('' '', 1000));
}
static void Test(string text)
{
bool expectedResult = text.Trim().Length == 0;
string title = string.Format("Length={0}, result={1}", text.Length,
expectedResult);
var results = TestSuite.Create(title, text, expectedResult)
/* .Add(x => x.Trim().Length == 0, "Trim().Length == 0")
.Add(x => x.Trim() == "", "Trim() == /"/"")
.Add(x => x.Trim().Equals(""), "Trim().Equals(/"/")")
.Add(x => x.Trim() == string.Empty, "Trim() == string.Empty")
.Add(x => x.Trim().Equals(string.Empty), "Trim().Equals(string.Empty)")
*/
.Add(OriginalIsEmptyOrWhitespace)
.Add(IsEmptyOrWhitespaceForLoop)
.Add(IsEmptyOrWhitespaceForLoopReversed)
.Add(IsEmptyOrWhitespaceForLoopHoistedLength)
.RunTests()
.ScaleByBest(ScalingMode.VaryDuration);
results.Display(ResultColumns.NameAndDuration | ResultColumns.Score,
results.FindBest());
}
public static bool OriginalIsEmptyOrWhitespace(string text)
{
if (text.Length == 0)
{
return true;
}
foreach (char c in text)
{
if (c=='' '' || c==''/t'' || c==''/r'' || c==''/n'')
{
continue;
}
return false;
}
return true;
}
public static bool IsEmptyOrWhitespaceForLoop(string text)
{
for (int i=0; i < text.Length; i++)
{
char c = text[i];
if (c=='' '' || c==''/t'' || c==''/r'' || c==''/n'')
{
continue;
}
return false;
}
return true;
}
public static bool IsEmptyOrWhitespaceForLoopReversed(string text)
{
for (int i=text.Length-1; i >= 0; i--)
{
char c = text[i];
if (c=='' '' || c==''/t'' || c==''/r'' || c==''/n'')
{
continue;
}
return false;
}
return true;
}
public static bool IsEmptyOrWhitespaceForLoopHoistedLength(string text)
{
int length = text.Length;
for (int i=0; i < length; i++)
{
char c = text[i];
if (c=='' '' || c==''/t'' || c==''/r'' || c==''/n'')
{
continue;
}
return false;
}
return true;
}
}
Resultados:
============ Length=0, result=True ============
OriginalIsEmptyOrWhitespace 30.012 1.00
IsEmptyOrWhitespaceForLoop 30.802 1.03
IsEmptyOrWhitespaceForLoopReversed 32.944 1.10
IsEmptyOrWhitespaceForLoopHoistedLength 35.113 1.17
============ Length=1, result=True ============
OriginalIsEmptyOrWhitespace 31.150 1.04
IsEmptyOrWhitespaceForLoop 30.051 1.00
IsEmptyOrWhitespaceForLoopReversed 31.602 1.05
IsEmptyOrWhitespaceForLoopHoistedLength 33.383 1.11
============ Length=3, result=False ============
OriginalIsEmptyOrWhitespace 30.221 1.00
IsEmptyOrWhitespaceForLoop 30.131 1.00
IsEmptyOrWhitespaceForLoopReversed 34.502 1.15
IsEmptyOrWhitespaceForLoopHoistedLength 35.690 1.18
============ Length=1, result=False ============
OriginalIsEmptyOrWhitespace 31.626 1.05
IsEmptyOrWhitespaceForLoop 30.005 1.00
IsEmptyOrWhitespaceForLoopReversed 32.383 1.08
IsEmptyOrWhitespaceForLoopHoistedLength 33.666 1.12
============ Length=1000, result=False ============
OriginalIsEmptyOrWhitespace 30.177 1.00
IsEmptyOrWhitespaceForLoop 33.207 1.10
IsEmptyOrWhitespaceForLoopReversed 30.867 1.02
IsEmptyOrWhitespaceForLoopHoistedLength 31.837 1.06
============ Length=1002, result=False ============
OriginalIsEmptyOrWhitespace 30.217 1.01
IsEmptyOrWhitespaceForLoop 30.026 1.00
IsEmptyOrWhitespaceForLoopReversed 34.162 1.14
IsEmptyOrWhitespaceForLoopHoistedLength 34.860 1.16
============ Length=1000, result=True ============
OriginalIsEmptyOrWhitespace 30.303 1.01
IsEmptyOrWhitespaceForLoop 30.018 1.00
IsEmptyOrWhitespaceForLoopReversed 35.475 1.18
IsEmptyOrWhitespaceForLoopHoistedLength 40.927 1.36
Realmente no sé cuál es más rápido; aunque mi instinto me dice que es el número uno. Pero aquí hay otro método:
if (String.IsNullOrEmpty(myString.Trim()))
Verificar la longitud de una cadena por ser cero es la forma más eficiente de probar una cadena vacía, así que diría el número 1:
if (myString.Trim().Length == 0)
La única forma de optimizar esto podría ser evitar el recorte mediante el uso de una expresión regular compilada (Editar: esto es mucho más lento que usar Trim (). Longitud).
Editar: La sugerencia de usar Length proviene de una guía de FxCop. También lo probé: es 2-3 veces más rápido que compararlo con una cadena vacía. Sin embargo, ambos enfoques son extremadamente rápidos (estamos hablando de nanosegundos), así que no importa cuál uses. Recortar es mucho más un cuello de botella, es cientos de veces más lento que la comparación real al final.
Editar: Nuevas pruebas:
Test orders:
x. Test name
Ticks: xxxxx //Empty String
Ticks: xxxxx //two space
Ticks: xxxxx //single letter
Ticks: xxxxx //single letter with space
Ticks: xxxxx //long string
Ticks: xxxxx //long string with space
1. if (myString.Trim().Length == 0)
ticks: 4121800
ticks: 7523992
ticks: 17655496
ticks: 29312608
ticks: 17302880
ticks: 38160224
2. if (myString.Trim() == "")
ticks: 4862312
ticks: 8436560
ticks: 21833776
ticks: 32822200
ticks: 21655224
ticks: 42358016
3. if (myString.Trim().Equals(""))
ticks: 5358744
ticks: 9336728
ticks: 18807512
ticks: 30340392
ticks: 18598608
ticks: 39978008
4. if (myString.Trim() == String.Empty)
ticks: 4848368
ticks: 8306312
ticks: 21552736
ticks: 32081168
ticks: 21486048
ticks: 41667608
5. if (myString.Trim().Equals(String.Empty))
ticks: 5372720
ticks: 9263696
ticks: 18677728
ticks: 29634320
ticks: 18551904
ticks: 40183768
6. if (IsEmptyOrWhitespace(myString)) //See John Skeet''s Post for algorithm
ticks: 6597776
ticks: 9988304
ticks: 7855664
ticks: 7826296
ticks: 7885200
ticks: 7872776
7. is (string.IsNullOrEmpty(myString.Trim()) //Cloud''s suggestion
ticks: 4302232
ticks: 10200344
ticks: 18425416
ticks: 29490544
ticks: 17800136
ticks: 38161368
Y el código utilizado:
public void Main()
{
string res = string.Empty;
for (int j = 0; j <= 5; j++) {
string myString = "";
switch (j) {
case 0:
myString = "";
break;
case 1:
myString = " ";
break;
case 2:
myString = "x";
break;
case 3:
myString = "x ";
break;
case 4:
myString = "this is a long string for testing triming empty things.";
break;
case 5:
myString = "this is a long string for testing triming empty things. ";
break;
}
bool result = false;
Stopwatch sw = new Stopwatch();
sw.Start();
for (int i = 0; i <= 100000; i++) {
result = myString.Trim().Length == 0;
}
sw.Stop();
res += "ticks: " + sw.ElapsedTicks + Environment.NewLine;
}
Console.ReadKey(); //break point here to get the results
}
myString.Trim (). Length == 0 Tomó: 421 ms
myString.Trim () == '''' tomó: 468 ms
if (myString.Trim (). Equals ("") Tomó: 515 ms
if (myString.Trim () == String.Empty) Tomó: 484 ms
if (myString.Trim (). Equals (String.Empty)) Tomó: 500 ms
if (string.IsNullOrEmpty (myString.Trim ())) Tomó: 437 ms
En mis pruebas, parece myString.Trim (). Length == 0 y, sorprendentemente, string.IsNullOrEmpty (myString.Trim ()) fueron consistentemente los más rápidos. Los resultados anteriores son un resultado típico de hacer 10,000,000 comparaciones.
public static bool IsNullOrEmpty(this String str, bool checkTrimmed)
{
var b = String.IsNullOrEmpty(str);
return checkTrimmed ? b && str.Trim().Length == 0 : b;
}
String.IsNullOrWhitespace en .NET 4 Beta 2 también se reproduce en este espacio y no necesita ser escrito a medida
Como acabo de comenzar, no puedo comentar, así que aquí está.
if (String.IsNullOrEmpty(myString.Trim()))
Trim()
llamada Trim()
fallará si myString es nulo, ya que no puede invocar métodos en un objeto que es nulo ( NullReferenceException ).
Entonces, la sintaxis correcta sería algo como esto:
if (!String.IsNullOrEmpty(myString))
{
string trimmedString = myString.Trim();
//do the rest of you code
}
else
{
//string is null or empty, don''t bother processing it
}