2010-02-25 22 views
54

Si tengo una declaración de mayúsculas y minúsculas en la que el objeto en el conmutador es una cadena, ¿es posible hacer de todos modos ignorar la comparación de caso?Cómo hacer que la declaración del conmutador C# use IgnoreCase

que tienen por ejemplo:

string s = "house"; 
switch (s) 
{ 
    case "houSe": s = "window"; 
} 

Will s get valor "ventana". ¿Cómo anular la declaración de mayúsculas y minúsculas para que compare las cadenas usando ignoreCase?

Respuesta

49

como pareces ser consciente, en minúscula dos cadenas y comparándolos no es lo mismo que hacer una comparación ignore-case. Hay muchas razones para esto. Por ejemplo, el estándar Unicode permite que el texto con signos diacríticos se codifique de múltiples maneras. Algunos caracteres incluyen tanto el carácter base como el signo diacrítico en un único punto de código. Estos caracteres también pueden representarse como el carácter base seguido de un carácter diacrítico de combinación. Estas dos representaciones son iguales para todos los propósitos, y las comparaciones de cadenas con reconocimiento de cultura en .NET Framework las identificará correctamente como iguales, ya sea con CurrentCulture o InvariantCulture (con o sin IgnoreCase). Una comparación ordinal, por otro lado, los considerará incorrectamente como desiguales.

Desafortunadamente, switch no hace más que una comparación ordinal. Una comparación ordinal es adecuada para ciertos tipos de aplicaciones, como el análisis de un archivo ASCII con códigos rígidamente definidos, pero la comparación de cadenas ordinales es incorrecta para la mayoría de los demás usos.

Lo que he hecho en el pasado para obtener el comportamiento correcto es simplemente simular mi propia declaración de cambio. Hay muchas formas de hacer esto. Una forma sería crear un List<T> de pares de cadenas de casos y delegados. La lista se puede buscar utilizando la comparación de cadenas adecuada. Cuando se encuentra la coincidencia, se puede invocar al delegado asociado.

Otra opción es hacer la cadena obvia de instrucciones if. Esto generalmente no es tan malo como parece, ya que la estructura es muy regular.

Lo bueno de esto es que no hay realmente ninguna penalización en el rendimiento en la imitación de su propia funcionalidad de conmutación cuando se compara con cadenas. El sistema no creará una tabla de salto O (1) de la manera que sea posible con números enteros, por lo que de todos modos se compararán cada cadena de a una.

Si hay muchos casos para comparar y el rendimiento es un problema, la opción List<T> descrita anteriormente podría reemplazarse con un diccionario ordenado o una tabla hash. Entonces, el rendimiento puede coincidir o superar la opción de declaración de cambio.

Aquí es un ejemplo de la lista de delegados:

delegate void CustomSwitchDestination(); 
List<KeyValuePair<string, CustomSwitchDestination>> customSwitchList; 
CustomSwitchDestination defaultSwitchDestination = new CustomSwitchDestination(NoMatchFound); 
void CustomSwitch(string value) 
{ 
    foreach (var switchOption in customSwitchList) 
     if (switchOption.Key.Equals(value, StringComparison.InvariantCultureIgnoreCase)) 
     { 
      switchOption.Value.Invoke(); 
      return; 
     } 
    defaultSwitchDestination.Invoke(); 
} 

Por supuesto, es probable que desee añadir algunos parámetros estándar y, posiblemente, un tipo de retorno al delegado CustomSwitchDestination. ¡Y querrás hacer mejores nombres!

Si el comportamiento de cada uno de sus casos no es susceptible de delegar la invocación de esta manera, por ejemplo, si son necesarios diferentes parámetros, entonces queda bloqueado con las sentencias if encadenadas. También he hecho esto algunas veces.

if (s.Equals("house", StringComparison.InvariantCultureIgnoreCase)) 
    { 
     s = "window"; 
    } 
    else if (s.Equals("business", StringComparison.InvariantCultureIgnoreCase)) 
    { 
     s = "really big window"; 
    } 
    else if (s.Equals("school", StringComparison.InvariantCultureIgnoreCase)) 
    { 
     s = "broken window"; 
    } 
+4

A menos que esté equivocado, los dos solo son diferentes para ciertas culturas (como el turco), y en ese caso no podría usar 'ToUpperInvariant()' o 'ToLowerInvariant()'? Además, no está comparando _dos cadenas desconocidas_, está comparando una cadena desconocida con una cadena conocida. Por lo tanto, siempre que sepa cómo codificar la representación correcta de mayúsculas y minúsculas, entonces el bloque de interruptores debería funcionar bien. –

+5

@Seth Petry-Johnson - Tal vez esa optimización podría hacerse, pero la razón por la cual las opciones de comparación de cadenas están integradas en el marco es para que no todos tengamos que convertirnos en expertos en lingüística para escribir software correcto y extensible. –

+34

OK. Daré un ejemplo donde esto es relivante. Supongamos que en lugar de "casa" tuviéramos la palabra (¡inglés!) "Café". Este valor podría representarse igualmente bien (y con la misma probabilidad) por "caf \ u00E9" o "cafetería \ u0301". La igualdad ordinal (como en una sentencia switch) con 'ToLower()' o 'ToLowerInvariant()' devolverá falso. 'Equals' con' StringComparison.InvariantCultureIgnoreCase' devolverá true. Dado que ambas secuencias se ven idénticas cuando se muestran, la versión 'ToLower()' es un error desagradable para rastrear. Es por eso que siempre es mejor hacer comparaciones de cuerdas adecuadas, incluso si no eres turco. –

59

Un enfoque más simple es simplemente la minúscula de su cadena antes de entrar en la instrucción de cambio, y tener los casos más bajos.

En realidad, la parte superior es un poco mejor desde un punto de vista de rendimiento extremo de nanosegundos, pero menos natural de mirar.

ej .:

string s = "house"; 
switch (s.ToLower()) { 
    case "house": 
    s = "window"; 
    break; 
} 
+0

@Nick, ¿tiene alguna referencia al motivo de la diferencia de rendimiento entre las conversiones inferior y superior? No desafiarlo simplemente curioso. – Lazarus

+1

Sí, entiendo que la minoría es una forma, pero quiero que ignore la Causa. ¿Hay alguna manera de que pueda anular la declaración de cambio de mayúsculas y minúsculas? – Tolsan

+6

@Lazarus - Esto es de CLR a través de C#, se publicó aquí hace un tiempo en el hilo de características ocultas también: http://stackoverflow.com/questions/9033/hidden-features-of-c/12137#12137 Usted puede encender LinqPad con unos pocos millones de iteraciones, es cierto. –

20

En algunos casos, podría ser una buena idea usar una enumeración. Por lo tanto, primero analizaremos la enumeración (con ignorar indicador de casilla verdadero) y haremos un cambio en la enumeración.

SampleEnum Result; 
bool Success = SampleEnum.TryParse(inputText, true, out Result); 
if(!Success){ 
    //value was not in the enum values 
}else{ 
    switch (Result) { 
     case SampleEnum.Value1: 
     break; 
     case SampleEnum.Value2: 
     break; 
     default: 
     //do default behaviour 
     break; 
    } 
} 
+0

Solo una nota: Enum TryParse parece estar disponible con Framework 4.0 y forward, FYI. http://msdn.microsoft.com/en-us/library/dd991317(v=vs.100).aspx – granadaCoder

+0

Prefiero esta solución ya que desalienta el uso de cadenas mágicas. – user1069816

11

Una posible forma sería utilizar un diccionario de casos ignorados con un delegado de acción.

string s = null; 
var dic = new Dictionary<string, Action>(StringComparer.CurrentCultureIgnoreCase) 
{ 
    {"house", () => s = "window"}, 
    {"house2",() => s = "window2"} 
}; 

dic["HouSe"](); 
+0

esta solución debería ser más alta ... – vipero07

+1

En lugar de 'CurrentCultureIgnoreCase', [' OrdinalIgnoreCase'] (https://msdn.microsoft.com/en-us/library/ms973919.aspx) es preferible. –

1

espero que esto ayuda a tratar de convertir toda la cadena en particular, cuando una de las minúsculas o mayúsculas y utilizar la cadena en minúsculas para la comparación:

public string ConvertMeasurements(string unitType, string value) 
{ 
    switch (unitType.ToLower()) 
    { 
     case "mmol/l": return (Double.Parse(value) * 0.0555).ToString(); 
     case "mg/dl": return (double.Parse(value) * 18.0182).ToString(); 
    } 
} 
6

Lo siento por este nuevo puesto a una vieja pregunta , pero hay una nueva opción para resolver este problema usando C# 7 (VS 2017).

C# 7 ofrece ahora "coincidencia de patrones", y que puede ser utilizado para abordar esta cuestión así:

string houseName = "house"; // value to be tested, ignoring case 
string windowName; // switch block will set value here 

switch (true) 
{ 
    case bool b when houseName.Equals("MyHouse", StringComparison.InvariantCultureIgnoreCase): 
     windowName = "MyWindow"; 
     break; 
    case bool b when houseName.Equals("YourHouse", StringComparison.InvariantCultureIgnoreCase): 
     windowName = "YourWindow"; 
     break; 
    case bool b when houseName.Equals("House", StringComparison.InvariantCultureIgnoreCase): 
     windowName = "Window"; 
     break; 
    default: 
     windowName = null; 
} 

Esta solución también se ocupa de la cuestión mencionada en la respuesta por @Jeffrey L Whitledge que caso- La comparación insensible de las cadenas no es lo mismo que comparar dos cadenas de cassettes inferiores.

Por cierto, hubo un artículo interesante en febrero de 2017 en Visual Studio Magazine que describe la coincidencia de patrones y cómo se puede usar en los bloques de casos. Por favor, eche un vistazo: Pattern Matching in C# 7.0 Case Blocks

+0

Sería más largo, pero preferiría 'cambiar (nombre de la casa)' y luego hacer la comparación similar a la forma en que lo hizo, es decir, 'nombre de caso var cuando name.Equals (" MyHouse ", ...' – LewisM

+0

@LewisM - Eso es interesante. ¿Puedes mostrar un ejemplo de eso? – STLDeveloper

0

Una extensión de la respuesta de @STLDeveloperA. Una nueva forma de hacer la evaluación declaración sin múltiples sentencias if como de C# 7 está utilizando el patrón de comparación de sentencias Switch, similar a la forma @STLDeveloper pesar de esta manera es el cambio en la variable que se está conmutada

string houseName = "house"; // value to be tested 
string s; 
switch (houseName) 
{ 
    case var name when name.Equals("Bungalow", StringComparison.InvariantCultureIgnoreCase): 
     s = "Single glazed"; 
    break; 

    case var name when name.Equals("Church", StringComparison.InvariantCultureIgnoreCase); 
     s = "Stained glass"; 
     break; 
     ... 
    default: 
     s = "No windows (cold or dark)" 
     break; 
} 

La revista Visual Studio tiene un nice article on pattern matching case blocks que valdría la pena ver.

Cuestiones relacionadas