Jak zdajesz się być świadomy, zmniejszanie dwóch liter i porównywanie ich nie jest tym samym, co porównywanie wielkości liter. Jest ku temu wiele powodów. Na przykład standard Unicode umożliwia kodowanie tekstu ze znakami diakrytycznymi na wiele sposobów. Niektóre znaki zawierają zarówno znak podstawowy, jak i znak diakrytyczny w jednym punkcie kodowym. Znaki te mogą być również przedstawiane jako znak podstawowy, po którym następuje łączący znak diakrytyczny. Te dwie reprezentacje są równe we wszystkich celach, a porównania ciągów uwzględniające kulturę w .NET Framework poprawnie zidentyfikują 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 porównaniem porządkowym. Porównanie porządkowe jest dobre w przypadku niektórych rodzajów aplikacji, takich jak analiza pliku ASCII ze sztywno zdefiniowanymi kodami, ale w większości innych zastosowań porównanie ciągów porządkowych jest niewłaściwe.
To, co zrobiłem w przeszłości, aby uzyskać prawidłowe zachowanie, to po prostu makieta własnej instrukcji przełącznika. Można to zrobić na wiele sposobów. Jednym ze sposobów byłoby utworzenie List<T>
par ciągów znaków i delegatów. Listę można przeszukiwać za pomocą odpowiedniego porównania ciągów. Po znalezieniu dopasowania można wywołać skojarzonego delegata.
Inną opcją jest wykonanie oczywistego łańcucha if
instrukcji. Zwykle okazuje się, że nie jest tak źle, jak się wydaje, ponieważ struktura jest bardzo regularna.
Wspaniałą rzeczą w tym jest to, że tak naprawdę nie ma żadnego spadku wydajności w tworzeniu własnych funkcji przełącznika podczas porównywania z ciągami. System nie utworzy tablicy skoków O (1) w taki sposób, w jaki może to zrobić z liczbami całkowitymi, więc i tak będzie porównywał każdy łańcuch pojedynczo.
Jeśli istnieje wiele przypadków do porównania, a wydajność jest problemem, wówczas List<T>
opisaną powyżej opcję można zastąpić posortowanym słownikiem lub tabelą skrótów. Wtedy wydajność może potencjalnie odpowiadać lub przekraczać opcję instrukcji switch.
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 będziesz chciał dodać kilka standardowych parametrów i prawdopodobnie typ zwracany do delegata CustomSwitchDestination. I będziesz chciał tworzyć lepsze nazwy!
Jeśli zachowanie każdego z twoich przypadków nie pozwala na delegowanie wywołania w ten sposób, na przykład jeśli potrzebne są różne parametry, to utkniesz z połączonymi if
instrukcjami. 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";
}
ToUpperInvariant()
lubToLowerInvariant()
? Poza tym nie porównuje dwóch nieznanych ciągów , porównuje jeden nieznany ciąg z jednym znanym. Tak więc, o ile wie, jak zakodować na stałe odpowiednią reprezentację wielkich lub małych liter, blok przełącznika powinien działać dobrze.ToLower()
alboToLowerInvariant()
zwróci wartość false.Equals
zStringComparison.InvariantCultureIgnoreCase
zwróci prawdę. Ponieważ obie sekwencje wyglądają identycznie po wyświetleniu,ToLower()
wersja jest paskudnym błędem do wyśledzenia. Dlatego zawsze najlepiej jest przeprowadzać właściwe porównania ciągów, nawet jeśli nie jesteś Turkiem.Prostszym podejściem jest po prostu zmniejszenie wielkości liter przed przejściem do instrukcji switch i zmniejszenie wielkości liter.
Właściwie cholewka jest nieco lepsza z czysto ekstremalnego punktu widzenia nanosekund, ale mniej naturalnie na nią patrzeć.
Na przykład:
string s = "house"; switch (s.ToLower()) { case "house": s = "window"; break; }
źródło
ToUpper(Invariant)
to nie tylko szybsze, ale bardziej wiarygodne: stackoverflow.com/a/2801521/67824Przepraszamy za ten nowy wpis dotyczący starego pytania, ale jest nowa opcja rozwiązania tego problemu za pomocą C # 7 (VS 2017).
C # 7 oferuje teraz „dopasowywanie wzorców” i może być użyte do rozwiązania tego problemu w ten sposób:
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; break; }
To rozwiązanie rozwiązuje również problem wspomniany w odpowiedzi @Jeffrey L Whitledge'a, że porównanie ciągów bez uwzględniania wielkości liter to nie to samo, co porównanie dwóch napisów z małymi literami.
Nawiasem mówiąc, w lutym 2017 w Visual Studio Magazine pojawił się interesujący artykuł opisujący dopasowywanie wzorców i sposób ich wykorzystania w blokach wielkości liter. Proszę spojrzeć: Dopasowywanie wzorców w blokach wielkości liter C # 7.0
EDYTOWAĆ
W świetle odpowiedzi @ LewisM ważne jest, aby podkreślić, że
switch
stwierdzenie ma nowe, interesujące zachowanie. Oznacza to, że jeśli twojacase
instrukcja zawiera deklarację zmiennej, to wartość określona wswitch
części jest kopiowana do zmiennej zadeklarowanej wcase
. W poniższym przykładzie wartośćtrue
jest kopiowana do zmiennej lokalnejb
. Ponadto zmiennab
jest nieużywana i istnieje tylko po to, abywhen
klauzula docase
instrukcji mogła istnieć:switch(true) { case bool b when houseName.Equals("X", StringComparison.InvariantCultureIgnoreCase): windowName = "X-Window";): break; }
Jak wskazuje @LewisM, można to wykorzystać na korzyść - ta korzyść polega na tym, że porównywana rzecz jest faktycznie w
switch
zestawieniu, tak jak ma to miejsce w przypadku klasycznego użyciaswitch
oświadczenia. Ponadto tymczasowe wartości zadeklarowane wcase
instrukcji mogą zapobiec niechcianym lub niezamierzonym zmianom pierwotnej wartości:switch(houseName) { case string hn when hn.Equals("X", StringComparison.InvariantCultureIgnoreCase): windowName = "X-Window"; break; }
źródło
switch (houseName)
wtedy zrobić porównanie podobnie jak Ty to zrobiłeś, czylicase var name when name.Equals("MyHouse", ...
switch
wartości argumentów docase
zmiennych tymczasowych.case { } when
sposób, aby nie martwić się o typ i nazwę zmiennej.W niektórych przypadkach dobrym pomysłem może być użycie wyliczenia. Więc najpierw przeanalizuj wyliczenie (z flagą ignoreCase true), a następnie włącz przełącznik wyliczenia.
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; } }
źródło
Rozszerzenie odpowiedzi autorstwa @STLDeveloperA. Nowy sposób obliczania instrukcji bez wielu instrukcji if od C # 7 polega na użyciu instrukcji Switch pasującej do wzorca, podobnie do sposobu, w jaki @STLDeveloper, chociaż w ten sposób włącza przełączaną zmienną
string houseName = "house"; // value to be tested string s; switch (houseName) { case var name when string.Equals(name, "Bungalow", StringComparison.InvariantCultureIgnoreCase): s = "Single glazed"; break; case var name when string.Equals(name, "Church", StringComparison.InvariantCultureIgnoreCase): s = "Stained glass"; break; ... default: s = "No windows (cold or dark)"; break; }
Magazyn Visual Studio zawiera fajny artykuł na temat bloków obudów dopasowanych do wzorów, który może być wart obejrzenia.
źródło
switch
zestawienia.case var name when "Bungalow".Equals(name, StringComparison.InvariantCultureIgnoreCase):
ponieważ może to zapobiec zerowemu wyjątkowi odniesienia (gdzie houseName ma wartość null) lub alternatywnie dodać najpierw przypadek, w którym łańcuch ma wartość null.Jednym z możliwych sposobów byłoby użycie słownika ignorowania wielkości liter z delegatem akcji.
string s = null; var dic = new Dictionary<string, Action>(StringComparer.CurrentCultureIgnoreCase) { {"house", () => s = "window"}, {"house2", () => s = "window2"} }; dic["HouSe"]();
// Zwróć uwagę, że wywołanie nie zwraca tekstu, a jedynie wypełnia lokalną zmienną s.
// Jeśli chcesz zwrócić rzeczywisty tekst, zamień
Action
naFunc<string>
i wartości w słowniku na coś podobnego() => "window2"
źródło
CurrentCultureIgnoreCase
,OrdinalIgnoreCase
jest korzystne.Oto rozwiązanie, które zawija rozwiązanie @Magnus w klasę:
public class SwitchCaseIndependent : IEnumerable<KeyValuePair<string, Action>> { private readonly Dictionary<string, Action> _cases = new Dictionary<string, Action>(StringComparer.OrdinalIgnoreCase); public void Add(string theCase, Action theResult) { _cases.Add(theCase, theResult); } public Action this[string whichCase] { get { if (!_cases.ContainsKey(whichCase)) { throw new ArgumentException($"Error in SwitchCaseIndependent, \"{whichCase}\" is not a valid option"); } //otherwise return _cases[whichCase]; } } public IEnumerator<KeyValuePair<string, Action>> GetEnumerator() { return _cases.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return _cases.GetEnumerator(); } }
Oto przykład użycia go w prostej aplikacji Windows Form:
var mySwitch = new SwitchCaseIndependent { {"hello", () => MessageBox.Show("hello")}, {"Goodbye", () => MessageBox.Show("Goodbye")}, {"SoLong", () => MessageBox.Show("SoLong")}, }; mySwitch["HELLO"]();
Jeśli używasz lambd (jak w przykładzie), otrzymujesz domknięcia, które przechwytują twoje zmienne lokalne (bardzo zbliżone do wrażenia, jakie daje instrukcja switch).
Ponieważ używa Dictionary pod okładkami, uzyskuje zachowanie O (1) i nie polega na przechodzeniu przez listę ciągów. Oczywiście musisz zbudować ten słownik, a to prawdopodobnie kosztuje więcej.
Prawdopodobnie miałoby sens dodanie prostej
bool ContainsCase(string aCase)
metody, która po prostu wywołujeContainsKey
metodę słownika .źródło
Mam nadzieję, że pomoże to w próbie przekonwertowania całego ciągu na określone litery, zarówno z małych, jak i dużych liter i użyj ciągu małych liter do 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(); } }
źródło
Powinno to wystarczyć:
string s = "houSe"; switch (s.ToLowerInvariant()) { case "house": s = "window"; break; }
Porównanie przełączników jest zatem niezmienne kulturowo. O ile widzę, powinno to dać taki sam wynik jak rozwiązania C # 7 z dopasowywaniem wzorców, ale bardziej zwięźle.
źródło