So erstellen Sie eine C # -Schalteranweisung mit IgnoreCase

91

Wenn ich eine switch-case-Anweisung habe, bei der das Objekt im switch eine Zeichenfolge ist, ist es dann möglich, einen ignoreCase-Vergleich durchzuführen?

Ich habe zum Beispiel:

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

Wird sder Wert "Fenster" erhalten? Wie überschreibe ich die switch-case-Anweisung, damit die Zeichenfolgen mit ignoreCase verglichen werden?

Tolsan
quelle

Antworten:

64

Wie Sie anscheinend wissen, ist das Verringern und Vergleichen von zwei Zeichenfolgen nicht dasselbe wie das Vergleichen von Groß- und Kleinschreibung. Dafür gibt es viele Gründe. Der Unicode-Standard ermöglicht beispielsweise die mehrfache Codierung von Text mit diakritischen Zeichen. Einige Zeichen enthalten sowohl das Basiszeichen als auch das diakritische Zeichen in einem einzelnen Codepunkt. Diese Zeichen können auch als Basiszeichen dargestellt werden, gefolgt von einem kombinierten diakritischen Zeichen. Diese beiden Darstellungen sind für alle Zwecke gleich, und die kulturbewussten Zeichenfolgenvergleiche in .NET Framework identifizieren sie korrekt als gleich, entweder mit der CurrentCulture oder der InvariantCulture (mit oder ohne IgnoreCase). Ein Ordnungsvergleich hingegen betrachtet sie fälschlicherweise als ungleich.

Tut leider switchnichts anderes als einen ordinalen Vergleich. Ein Ordnungsvergleich ist für bestimmte Arten von Anwendungen in Ordnung, z. B. das Parsen einer ASCII-Datei mit fest definierten Codes, aber der Ordnungszeichenfolgenvergleich ist für die meisten anderen Anwendungen falsch.

Was ich in der Vergangenheit getan habe, um das richtige Verhalten zu erzielen, ist nur meine eigene switch-Anweisung zu verspotten. Es gibt viele Möglichkeiten, dies zu tun. Eine Möglichkeit wäre, ein List<T>Paar von Fallzeichenfolgen und Delegaten zu erstellen . Die Liste kann mit dem richtigen Zeichenfolgenvergleich durchsucht werden. Wenn die Übereinstimmung gefunden wird, kann der zugeordnete Delegat aufgerufen werden.

Eine andere Möglichkeit besteht darin, die offensichtliche Kette von ifAussagen zu machen. Dies stellt sich normalerweise als nicht so schlecht heraus, wie es sich anhört, da die Struktur sehr regelmäßig ist.

Das Tolle daran ist, dass es keine wirklichen Leistungseinbußen gibt, wenn Sie Ihre eigene Switch-Funktionalität beim Vergleich mit Strings nachahmen. Das System wird eine O (1) -Sprungtabelle nicht so erstellen, wie es mit ganzen Zahlen möglich ist, daher wird es sowieso jede Zeichenfolge einzeln vergleichen.

Wenn viele Fälle verglichen werden müssen und die Leistung ein Problem darstellt, kann die List<T>oben beschriebene Option durch ein sortiertes Wörterbuch oder eine Hash-Tabelle ersetzt werden. Dann kann die Leistung möglicherweise mit der Option switch-Anweisung übereinstimmen oder diese überschreiten.

Hier ist ein Beispiel für die Liste der Delegierten:

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

Natürlich möchten Sie dem CustomSwitchDestination-Delegaten wahrscheinlich einige Standardparameter und möglicherweise einen Rückgabetyp hinzufügen. Und du wirst bessere Namen machen wollen!

Wenn das Verhalten jedes Ihrer Fälle nicht dazu geeignet ist, den Aufruf auf diese Weise zu delegieren, z. B. wenn unterschiedliche Parameter erforderlich sind, bleiben Sie bei verketteten ifAnweisungen hängen. Ich habe das auch ein paar Mal gemacht.

    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";
    }
Jeffrey L Whitledge
quelle
6
Wenn ich mich nicht irre, unterscheiden sich die beiden nur für bestimmte Kulturen (wie Türkisch), und in diesem Fall konnte er sie nicht verwenden ToUpperInvariant()oder ToLowerInvariant()? Außerdem vergleicht er nicht zwei unbekannte Zeichenfolgen , sondern eine unbekannte Zeichenfolge mit einer bekannten Zeichenfolge. Solange er weiß, wie man die geeignete Groß- oder Kleinschreibung fest codiert, sollte der Schaltblock einwandfrei funktionieren.
Seth Petry-Johnson
8
@ Seth Petry-Johnson - Vielleicht könnte diese Optimierung vorgenommen werden, aber der Grund, warum die Optionen für den String-Vergleich in das Framework integriert sind, ist, dass wir nicht alle Linguistik-Experten werden müssen, um korrekte, erweiterbare Software zu schreiben.
Jeffrey L Whitledge
59
IN ORDNUNG. Ich werde ein Beispiel geben, wo dies relivant ist. Angenommen, wir hätten anstelle von "Haus" das (englische!) Wort "Café". Dieser Wert könnte gleich gut (und gleich wahrscheinlich) entweder durch "caf \ u00E9" oder "cafe \ u0301" dargestellt werden. Ordinale Gleichheit (wie in einer switch-Anweisung) mit entweder ToLower()oder ToLowerInvariant()wird false zurückgeben. Equalsmit StringComparison.InvariantCultureIgnoreCasewird true zurückgeben. Da beide Sequenzen bei der Anzeige identisch aussehen, ist die ToLower()Version ein böser Fehler. Aus diesem Grund ist es immer am besten, korrekte Zeichenfolgenvergleiche durchzuführen, auch wenn Sie kein Türke sind.
Jeffrey L Whitledge
77

Ein einfacherer Ansatz besteht darin, die Zeichenfolge nur in Kleinbuchstaben zu schreiben, bevor sie in die switch-Anweisung aufgenommen wird, und die Groß- und Kleinschreibung zu verringern.

Tatsächlich ist das Obermaterial vom Standpunkt der extremen Nanosekundenleistung etwas besser, aber weniger natürlich anzusehen.

Z.B:

string s = "house"; 
switch (s.ToLower()) { 
  case "house": 
    s = "window"; 
    break;
}
Nick Craver
quelle
1
Ja, ich verstehe, dass Kleinbuchstaben ein Weg sind, aber ich möchte, dass es ignoreCase ist. Gibt es eine Möglichkeit, die switch-case-Anweisung zu überschreiben?
Tolsan
6
@Lazarus - Dies ist von CLR über C #. Es wurde vor einiger Zeit auch hier im Thread mit versteckten Funktionen veröffentlicht: stackoverflow.com/questions/9033/hidden-features-of-c/… Sie können LinqPad mit ein paar starten Millionen Iterationen, gilt.
Nick Craver
1
@Tolsan - Nein, leider gibt es nicht nur wegen seiner statischen Natur. Vor
einiger Zeit
9
Es scheint ToUpper(Invariant)nicht nur schneller, sondern auch zuverlässiger zu sein: stackoverflow.com/a/2801521/67824
Ohad Schneider
48

Entschuldigen Sie diesen neuen Beitrag zu einer alten Frage, aber es gibt eine neue Option zum Lösen dieses Problems mit C # 7 (VS 2017).

C # 7 bietet jetzt "Pattern Matching" und kann verwendet werden, um dieses Problem folgendermaßen zu beheben:

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

Diese Lösung befasst sich auch mit dem in der Antwort von @Jeffrey L Whitledge erwähnten Problem, dass der Vergleich von Zeichenfolgen ohne Berücksichtigung der Groß- und Kleinschreibung nicht mit dem Vergleich zweier Zeichenfolgen mit niedrigerem Gehäuse identisch ist.

Übrigens gab es im Februar 2017 im Visual Studio Magazine einen interessanten Artikel, in dem der Mustervergleich und seine Verwendung in Fallblöcken beschrieben wurden. Bitte werfen Sie einen Blick darauf: Pattern Matching in C # 7.0-Fallblöcken

BEARBEITEN

In Anbetracht der Antwort von @ LewisM ist es wichtig darauf hinzuweisen, dass die switchAussage ein neues, interessantes Verhalten aufweist. Das heißt, wenn Ihre caseAnweisung eine Variablendeklaration enthält, wird der im switchTeil angegebene Wert in die in der deklarierte Variable kopiert case. Im folgenden Beispiel wird der Wert truein die lokale Variable kopiert b. Darüber hinaus wird die Variable bnicht verwendet und existiert nur, damit die whenKlausel zur caseAnweisung existieren kann:

switch(true)
{
    case bool b when houseName.Equals("X", StringComparison.InvariantCultureIgnoreCase):
        windowName = "X-Window";):
        break;
}

Wie @LewisM hervorhebt, kann dies zum Nutzen genutzt werden - der Vorteil besteht darin, dass das zu vergleichende Objekt tatsächlich in der switchAussage enthalten ist, wie dies bei der klassischen Verwendung der switchAussage der Fall ist. Außerdem können die in der caseAnweisung deklarierten temporären Werte unerwünschte oder versehentliche Änderungen des ursprünglichen Werts verhindern:

switch(houseName)
{
    case string hn when hn.Equals("X", StringComparison.InvariantCultureIgnoreCase):
        windowName = "X-Window";
        break;
}
STLDev
quelle
2
Es würde länger switch (houseName)case var name when name.Equals("MyHouse", ...
dauern
@ LewisM - Das ist interessant. Können Sie ein funktionierendes Beispiel dafür zeigen?
STLDev
@ LewisM - tolle Antwort. Ich habe eine weitere Diskussion über die Zuweisung von switchArgumentwerten zu casetemporären Variablen hinzugefügt .
STLDev
Yay für Mustervergleich im modernen C #
Thiago Silva
Sie können auch "Objektmusterabgleich" verwenden, case { } whendamit Sie sich nicht um Variablentyp und -namen kümmern müssen.
Bob
32

In einigen Fällen kann es eine gute Idee sein, eine Aufzählung zu verwenden. Analysieren Sie also zuerst die Aufzählung (mit dem Flag ignoreCase true) und schalten Sie dann die Aufzählung ein.

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;
   }
}
uli78
quelle
Nur ein Hinweis: Enum TryParse scheint mit Framework 4.0 und Forward verfügbar zu sein. msdn.microsoft.com/en-us/library/dd991317(v=vs.100).aspx
granadaCoder
4
Ich bevorzuge diese Lösung, da sie von der Verwendung magischer Saiten abhält.
user1069816
23

Eine Erweiterung der Antwort von @STLDeveloperA. Eine neue Methode zur Bewertung von Anweisungen ohne mehrere if-Anweisungen ab c # 7 ist die Verwendung der Switch-Anweisung mit Mustervergleich, ähnlich wie bei @STLDeveloper, obwohl auf diese Weise die zu schaltende Variable eingeschaltet wird

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

Das Visual Studio Magazin hat einen schönen Artikel über Musterblöcke , die einen Blick wert sein könnten.

LewisM
quelle
Vielen Dank, dass Sie auf die zusätzliche Funktionalität der neuen switchAnweisung hingewiesen haben .
STLDev
5
+1 - Dies sollte die akzeptierte Antwort für die moderne Entwicklung (ab C # 7) sein. Eine Änderung, die ich vornehmen würde, ist, dass ich wie folgt codieren würde: case var name when "Bungalow".Equals(name, StringComparison.InvariantCultureIgnoreCase):Dies kann eine Nullreferenzausnahme verhindern (wobei houseName null ist) oder alternativ einen Fall hinzufügen, in dem die Zeichenfolge zuerst null ist.
Jay
20

Eine Möglichkeit wäre die Verwendung eines Wörterbuchs zum Ignorieren von Groß- und Kleinschreibung mit einem Aktionsdelegierten.

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

dic["HouSe"]();

// Beachten Sie, dass der Aufruf keinen Text zurückgibt, sondern nur lokale Variablen s auffüllt.
// Wenn Sie den eigentlichen Text zurückzukehren, ersetzen Actionzu Func<string>wie und Werte im Wörterbuch zu etwas() => "window2"

Magnus
quelle
4
Anstatt CurrentCultureIgnoreCase, OrdinalIgnoreCasebevorzugt.
Richard Ev
2
@richardEverett Bevorzugt? Hängt davon ab, was Sie wollen, wenn Sie die aktuelle Kultur ignorieren möchten, wird dies nicht bevorzugt.
Magnus
Wenn jemand interessiert ist, nimmt meine Lösung (unten) diese Idee und packt sie in eine einfache Klasse.
Flydog57
2

Hier ist eine Lösung, die die Lösung von @Magnus in eine Klasse einschließt:

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

Hier ist ein Beispiel für die Verwendung in einer einfachen Windows Form-App:

   var mySwitch = new SwitchCaseIndependent
   {
       {"hello", () => MessageBox.Show("hello")},
       {"Goodbye", () => MessageBox.Show("Goodbye")},
       {"SoLong", () => MessageBox.Show("SoLong")},
   };
   mySwitch["HELLO"]();

Wenn Sie Lambdas verwenden (wie im Beispiel), erhalten Sie Abschlüsse, die Ihre lokalen Variablen erfassen (ziemlich nahe an dem Gefühl, das Sie von einer switch-Anweisung erhalten).

Da es ein Wörterbuch unter der Decke verwendet, erhält es O (1) -Verhalten und muss nicht durch die Liste der Zeichenfolgen gehen. Natürlich müssen Sie dieses Wörterbuch erstellen, und das kostet wahrscheinlich mehr.

Es wäre wahrscheinlich sinnvoll, eine einfache bool ContainsCase(string aCase)Methode hinzuzufügen , die einfach die ContainsKeyMethode des Wörterbuchs aufruft .

Flydog57
quelle
1

Ich hoffe, dies hilft beim Versuch, die gesamte Zeichenfolge in Kleinbuchstaben oder Großbuchstaben umzuwandeln und die Zeichenfolge in Kleinbuchstaben zum Vergleich zu verwenden:

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

Dies sollte ausreichen:

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

Der Schaltervergleich ist dabei kulturinvariant. Soweit ich sehen kann, sollte dies das gleiche Ergebnis wie die C # 7-Pattern-Matching-Lösungen erzielen, jedoch prägnanter.

Kevin Bennett
quelle
-1

10 Jahre später können Sie mit dem C # -Musterabgleich Folgendes tun:

private string NormalisePropertyType(string propertyType) => true switch
{
    true when string.IsNullOrWhiteSpace(propertyType) => propertyType,
    true when "house".Equals(propertyType, StringComparison.OrdinalIgnoreCase) => "house",
    true when "window".Equals(propertyType, StringComparison.OrdinalIgnoreCase) => "window",
    true when "door".Equals(propertyType, StringComparison.OrdinalIgnoreCase) => "door",
    true when "roof".Equals(propertyType, StringComparison.OrdinalIgnoreCase) => "roof",
    true when "chair".Equals(propertyType, StringComparison.OrdinalIgnoreCase) => "chair",
    _ => propertyType
};
Sudara
quelle