2010-02-25 10 views
54

Jeśli mam instrukcji switch-case, gdzie obiekt w przełączniku jest ciąg, czy jest możliwe, aby tak czy inaczej porównać ignoreCase?Jak zrobić C# Switch Instrukcja używać IgnoreCase

mam na przykład:

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

S będzie wartość GET "okno". Jak zastąpić instrukcję switch-case, aby porównać ciągi za pomocą metody ignoreCase?

Odpowiedz

49

Jak wydaje się mieć świadomość, lowercasing dwa ciągi i porównywanie ich nie jest takie samo jak robi ignorowanych sprawę porównanie. Istnieje wiele powodów takiego stanu rzeczy. Na przykład standard Unicode umożliwia kodowanie znaków diakrytycznych na wiele sposobów. Niektóre znaki zawierają zarówno znak bazowy, jak i znak diakrytyczny w jednym punkcie kodowym. Znaki te można również przedstawić jako znak bazowy, a następnie łączący znak diakrytyczny. Te dwie reprezentacje są równe dla wszystkich celów, a porównania łańcuchów z właściwymi dla kultury w .NET Framework poprawnie identyfikują je jako równe, z CurrentCulture lub InvariantCulture (z lub bez IgnoreCase). Z drugiej strony porównanie porządkowe błędnie uzna je za nierówne.

Niestety, switch nie robi nic poza porządkowym porównaniem. Porównanie porządkowe jest dobre dla niektórych rodzajów aplikacji, takich jak przetwarzanie pliku ASCII ze sztywno zdefiniowanymi kodami, ale porządkowe porównywanie ciągów jest nieprawidłowe dla większości innych zastosowań.

To, co zrobiłem w przeszłości, aby uzyskać prawidłowe zachowanie, to po prostu wyśmiewać moją własną instrukcję przełączania. Jest na to wiele sposobów. Jednym ze sposobów byłoby utworzenie List<T> par ciągów znaków i delegatów. Lista może być przeszukiwana przy użyciu odpowiedniego porównania łańcuchów. Po znalezieniu powiązania może zostać wywołany powiązany delegat.

Inną opcją jest wykonanie oczywistego łańcucha oświadczeń if. Zwykle okazuje się, że nie jest tak źle, jak się wydaje, ponieważ struktura jest bardzo regularna.

Wspaniałą rzeczą jest to, że w porównaniu z ciągami nie ma żadnej kary za wydajność w kpieniu z własnej funkcji przełącznika. System nie wykona tabeli przeskoku O (1) tak, jak to możliwe z liczbami całkowitymi, więc i tak będzie porównywać każdy ciąg na raz.

Jeśli istnieje wiele przypadków do porównania, a wydajność jest problemem, opisana powyżej opcja List<T> może zostać zastąpiona posortowaną tablicą słownika lub tabeli mieszania. Wtedy wydajność może potencjalnie odpowiadać opcji instrukcji switch lub ją przekraczać.

Oto przykład listy delegatów:

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

Oczywiście, prawdopodobnie zechcesz dodać kilka standardowych parametrów i ewentualnie typ zwracany do delegata CustomSwitchDestination. I będziesz chciał tworzyć lepsze nazwy!

Jeśli zachowanie każdego z twoich przypadków nie jest możliwe do delegowania wywołania w ten sposób, na przykład jeśli konieczne są różne parametry, utkniesz w przyklejonych if statkach. Zrobiłem to również kilka razy.

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

O ile się nie mylę, te dwie rzeczy różnią się tylko w niektórych kulturach (np. po turecku) iw takim przypadku nie mógł użyć "ToUpperInvariant()" lub "ToLowerInvariant()"? Poza tym nie porównuje dwóch nieznanych ciągów, porównuje jeden nieznany ciąg do jednego znanego ciągu. Tak więc, o ile wie, jak kodować odpowiednią reprezentację górnej lub małej litery, blok przełącznika powinien działać poprawnie. –

+5

@Seth Petry-Johnson - Być może ta optymalizacja mogłaby być wykonana, ale powód, dla którego opcje porównywania ciągów są wypalane w ramach, jest tak, że nie wszyscy musimy stać się ekspertami lingwistycznymi, aby pisać poprawne, rozszerzalne oprogramowanie. –

+34

OK. Podam przykład, w którym jest on odpowiedni. Przypuśćmy, że zamiast "domu" mamy (angielski!) Słowo "kawiarnia". Ta wartość może być reprezentowana równie dobrze (i równie prawdopodobna) przez "caf \ u00E9" lub "cafe \ u0301". Równość porządkowa (jak w instrukcji switch) z 'ToLower()' lub 'ToLowerInvariant()' zwróci false. 'Equals' z' StringComparison.InvariantCultureIgnoreCase' zwróci wartość true. Ponieważ obie sekwencje wyglądają identycznie po wyświetleniu, wersja 'ToLower()' jest nieprzyjemnym błędem do wyśledzenia. Dlatego zawsze najlepiej jest robić odpowiednie porównania łańcuchów, nawet jeśli nie jesteś Turkiem. –

59

Prostszym podejściem jest po prostu obniżenie łańcucha przed przejściem do instrukcji switch i zmniejszenie liczby przypadków.

W rzeczywistości cholewka jest nieco lepsza z punktu widzenia ekstremalnej nanosekundy, ale mniej naturalna.

Np .:

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

@Nick, czy masz jakieś odniesienie do przyczyny różnicy w wydajności między konwersją dolną i górną? Nie podważaj tego po prostu ciekawy. – Lazarus

+1

Tak, rozumiem, że obniżenie jest sposobem, ale chcę, aby był ignorowany. Czy istnieje sposób, który może zastąpić instrukcji switch-case? – Tolsan

+6

@Lazarus - To jest z CLR przez C#, to zostało zamieszczone tutaj jeszcze raz w wątku ukrytych funkcji: http://stackoverflow.com/questions/9033/hidden-features-of-c/12137#12137 You może odpalić LinqPad z kilkoma milionami iteracji, to prawda. –

20

W niektórych przypadkach dobrym pomysłem może być użycie wyliczenia. Najpierw przeanalizuj enum (z flagą ignoreCase true) i włącz przełącznik enum.

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

Uwaga: program Enum TryParse wydaje się być dostępny w wersji 4.0 i do przodu, FYI. http://msdn.microsoft.com/en-us/library/dd991317(v=vs.100).aspx – granadaCoder

+0

Preferuję to rozwiązanie, ponieważ zniechęca do używania magicznych ciągów. – user1069816

11

Jednym ze sposobów jest użycie słownika case ignorowania z delegatem akcji.

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

dic["HouSe"](); 
+0

to rozwiązanie powinno być wyższe ... – vipero07

+1

Zamiast "CurrentCultureIgnoreCase" preferowana jest ['OrdininalIgnoreCase'] (https://msdn.microsoft.com/en-us/library/ms973919.aspx). –

1

Mam nadzieję, że to pomoże spróbuj przekonwertować cały ciąg w konkretnym przypadku zarówno małych liter lub górnej części obudowy i stosuje małą ciąg dla porównania:

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

żal tego nowego stanowiska do starej pytanie , ale jest nowa opcja rozwiązania tego problemu przy użyciu C# 7 (VS 2017).

C# 7 oferuje teraz „pasujące do wzorca”, a to może być wykorzystane do rozwiązania tego problemu wygląda następująco:

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

Rozwiązanie to dotyczy również sytuacji opisanej w odpowiedzi przez @Jeffrey L Whitledge tym CASE- Nieczułe porównanie łańcuchów to nie to samo co porównanie dwóch ciągów o małych literach.

Nawiasem mówiąc, w lutym 2017 roku w Visual Studio Magazine pojawił się ciekawy artykuł opisujący dopasowanie do wzorca i sposób, w jaki można go zastosować w blokach przypadku. Proszę spojrzeć: Pattern Matching in C# 7.0 Case Blocks

+0

Byłoby to dłużej, ale wolałbym "switch (houseName)", a następnie wykonaj porównanie podobne do tego, jak to zrobiłeś, tj. 'case var name when name.Equals (" MyHouse ", ...' – LewisM

+0

@LewisM - To interesujące. Czy możesz pokazać na to przykład? – STLDeveloper

0

Rozszerzenie odpowiedzi @STLDeveloperA. Nowy sposób na ocenę sprawozdania bez wielokrotności if jak C# 7 stosuje wzór pasujący switch, w podobny sposób @STLDeveloper chociaż w ten sposób przełącza się na bycie zmiennym włączony

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

magazynu Visual Studio ma numer nice article on pattern matching case blocks, który może być wart obejrzenia.