2010-02-25 9 views
54

Se ho un'istruzione switch-case in cui l'oggetto nel parametro è stringa, è possibile fare comunque ignorareCase compare?Come utilizzare l'istruzione switch C# con IgnoreCase

ho per esempio:

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

Will s valore get "finestra". Come sovrascrivere l'istruzione switch-case in modo da confrontare le stringhe usando ignoreCase?

risposta

49

Come ti sembra di essere a conoscenza, caratteri minuscoli due stringhe e confrontandoli non è lo stesso di fare un confronto ignorare e minuscole. Ci sono molte ragioni per questo. Ad esempio, lo standard Unicode consente al testo con segni diacritici di essere codificato in più modi. Alcuni caratteri includono sia il carattere di base che il segno diacritico in un singolo punto di codice. Questi personaggi possono anche essere rappresentati come il personaggio base seguito da un carattere diacritico che combina. Queste due rappresentazioni sono uguali per tutti gli scopi, e i confronti delle stringhe compatibili con la cultura in .NET Framework li identificheranno correttamente come uguali, con CurrentCulture o InvariantCulture (con o senza IgnoreCase). Un confronto ordinale, d'altro canto, li considererà erroneamente ineguali.

Sfortunatamente, switch non fa altro che un confronto ordinale. Un confronto ordinale va bene per alcuni tipi di applicazioni, come l'analisi di un file ASCII con codici rigidamente definiti, ma il confronto tra stringhe ordinali è sbagliato per la maggior parte degli altri usi.

Quello che ho fatto in passato per ottenere il comportamento corretto è solo prendere in giro la mia dichiarazione di switch. Ci sono molti modi per farlo. Un modo sarebbe creare uno List<T> di coppie di stringhe e delegati. L'elenco può essere cercato utilizzando il confronto di stringhe corretto. Quando viene trovata la corrispondenza, è possibile richiamare il delegato associato.

Un'altra opzione è fare l'ovvia catena di istruzioni if. Questo di solito risulta non essere così male come sembra, dato che la struttura è molto regolare.

La cosa grandiosa di questo è che non ci sono realmente penalità di prestazioni nel prendere in giro la propria funzionalità di switch quando si confrontano le stringhe. Il sistema non ha intenzione di creare una tabella di salto O (1) nel modo in cui può farlo con gli interi, quindi dovrà comunque confrontare ogni stringa una alla volta.

Se esistono molti casi da confrontare e le prestazioni rappresentano un problema, l'opzione List<T> descritta in precedenza potrebbe essere sostituita con un dizionario ordinato o una tabella hash. Quindi la prestazione potrebbe potenzialmente corrispondere o superare l'opzione di istruzione switch.

Ecco un esempio della lista dei delegati:

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(); 
} 

Certo, probabilmente si vorrà aggiungere alcuni parametri standard e, eventualmente, un tipo di ritorno al delegato CustomSwitchDestination. E vorresti fare nomi migliori!

Se il comportamento di ciascuno dei casi non è suscettibile di delegare il richiamo in questo modo, ad esempio se sono necessari parametri diversi, allora si è bloccati con le dichiarazioni concatenate if. L'ho fatto anche un paio di volte.

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 meno che non mi sbagli, i due sono diversi solo per alcune culture (come il turco), e in tal caso non potrebbe usare 'ToUpperInvariant()' o 'ToLowerInvariant()'? Inoltre, non sta confrontando _due stringhe sconosciute_, sta confrontando una stringa sconosciuta con una stringa nota. Quindi, purché sappia come hardcodificare la rappresentazione superiore o minuscola adatta, il blocco interruttore dovrebbe funzionare correttamente. –

+5

@Seth Petry-Johnson - Forse l'ottimizzazione potrebbe essere apportata, ma il motivo per cui le opzioni di comparazione delle stringhe sono inserite nel framework è che non tutti devono diventare esperti di linguistica per scrivere software corretto ed estensibile. –

+34

OK. Darò un esempio in cui è pertinente. Supponiamo che al posto di "casa" avessimo la parola "café" (inglese!). Questo valore potrebbe essere rappresentato ugualmente bene (e ugualmente probabile) da "caf \ u00E9" o "cafe \ u0301". L'uguaglianza ordinale (come in un'istruzione switch) con 'ToLower()' o 'ToLowerInvariant()' restituirà false. 'Equals' con' StringComparison.InvariantCultureIgnoreCase' restituirà true. Poiché entrambe le sequenze appaiono identiche quando visualizzate, la versione 'ToLower()' è un bug sgradevole da rintracciare. Questo è il motivo per cui è sempre meglio fare confronti con le stringhe corrette, anche se non sei turco. –

59

Un approccio più semplice è solo la scrittura in minuscolo della stringa prima che entri nell'istruzione switch e i casi siano inferiori.

In realtà, la tomaia è un po 'meglio da un punto di vista di prestazioni estreme nanosecondo, ma meno naturale da guardare.

Es .:

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

@Nick, hai qualche riferimento al motivo della differenza di prestazioni tra le conversioni inferiore e superiore? Non sfidarlo solo curioso. – Lazarus

+1

Sì, capisco che il minuscolo è un modo, ma voglio che ignori Case. C'è un modo in cui posso ignorare la dichiarazione caso-interruttore? – Tolsan

+6

@Lazarus - Questo è da CLR via C#, è stato pubblicato qui qualche tempo fa nel thread delle funzionalità nascoste pure: http://stackoverflow.com/questions/9033/hidden-features-of-c/12137#12137 You può attivare LinqPad con alcune milioni di iterazioni, è vero. –

20

In alcuni casi potrebbe essere una buona idea usare un enum. Quindi prima analizzare l'enum (con flag ignoreCase true) e avere un interruttore sull'enumerazione.

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 sembra essere disponibile con Framework 4.0 e forward, FYI. http://msdn.microsoft.com/en-us/library/dd991317(v=vs.100).aspx – granadaCoder

+0

Preferisco questa soluzione in quanto scoraggia l'uso di stringhe magiche. – user1069816

11

Un modo possibile sarebbe quello di utilizzare un dizionario del caso ignore con un delegato di azione.

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

dic["HouSe"](); 
+0

questa soluzione dovrebbe essere maggiore ... – vipero07

+1

Invece di 'CurrentCultureIgnoreCase', [' OrdinalIgnoreCase'] (https://msdn.microsoft.com/en-us/library/ms973919.aspx) è preferito. –

1

Spero che questo aiuta cercare di convertire l'intera stringa in caso particolare sia minuscole o maiuscole e utilizzare la stringa Minuscole per il confronto:

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

Ci scusiamo per questo nuovo post ad una vecchia questione , ma c'è una nuova opzione per risolvere questo problema usando C# 7 (VS 2017).

C# 7 offre ora "pattern matching", e può essere utilizzato per affrontare questo problema nel seguente modo:

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; 
} 

Questa soluzione affronta anche il problema menzionato nella risposta da @Jeffrey L Whitledge che caso- il confronto insensibile di stringhe non equivale al confronto di due stringhe con involucro inferiore.

A proposito, nel mese di febbraio 2017 in Visual Studio Magazine è stato pubblicato un articolo interessante che descrive la corrispondenza del modello e come può essere utilizzato nei blocchi del caso. Si prega di dare un'occhiata: Pattern Matching in C# 7.0 Case Blocks

+0

Sarebbe più lungo, ma preferirei 'passare (houseName)', quindi fare il confronto simile al modo in cui l'hai fatto, cioè 'nome var caso quando nome.Equals (" MyHouse ", ...' – LewisM

+0

@LisuM: È interessante. Puoi mostrare un esempio funzionante di questo? – STLDeveloper

0

Un'estensione alla risposta da @STLDeveloperA. Un nuovo modo per fare la valutazione economico senza multiplo se dichiarazioni come di C# 7 sta usando il pattern matching istruzione Switch, simile al modo @STLDeveloper se questo modo sta commutando sull'essere variabile commutata

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; 
} 

Lo studio visual magazine ha un nice article on pattern matching case blocks che potrebbe valere la pena dare un'occhiata.

Problemi correlati