Comment faire en sorte que l'instruction C # Switch utilise IgnoreCase

89

Si j'ai une instruction switch-case où l'objet dans le switch est une chaîne, est-il possible de faire une comparaison ignoreCase?

J'ai par exemple:

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

Obtiendra sla valeur "fenêtre"? Comment remplacer l'instruction switch-case pour qu'elle compare les chaînes à l'aide d'ignorerCase?

Tolsan
la source

Réponses:

63

Comme vous semblez le savoir, mettre deux chaînes en minuscules et les comparer n'est pas la même chose que de faire une comparaison sans casse. Il y a de nombreuses raisons à cela. Par exemple, la norme Unicode permet de coder du texte avec des signes diacritiques de plusieurs manières. Certains caractères incluent à la fois le caractère de base et le signe diacritique dans un seul point de code. Ces caractères peuvent également être représentés comme le caractère de base suivi d'un caractère diacritique de combinaison. Ces deux représentations sont égales à toutes fins, et les comparaisons de chaînes tenant compte de la culture dans le .NET Framework les identifieront correctement comme étant égales, avec CurrentCulture ou InvariantCulture (avec ou sans IgnoreCase). Une comparaison ordinale, en revanche, les considérera à tort comme inégales.

Malheureusement, switchne fait rien d'autre qu'une comparaison ordinale. Une comparaison ordinale convient à certains types d'applications, comme l'analyse d'un fichier ASCII avec des codes définis de manière rigide, mais la comparaison de chaînes ordinales est incorrecte pour la plupart des autres utilisations.

Ce que j'ai fait dans le passé pour obtenir le bon comportement, c'est juste une maquette de ma propre instruction switch. Il y a plusieurs manières de faire ça. Une façon serait de créer une List<T>paire de chaînes de cas et de délégués. La liste peut être recherchée en utilisant la comparaison de chaînes appropriée. Lorsque la correspondance est trouvée, le délégué associé peut être appelé.

Une autre option est de faire la chaîne évidente d' ifinstructions. Cela s'avère généralement moins grave qu'il n'y paraît, car la structure est très régulière.

La grande chose à ce sujet est qu'il n'y a pas vraiment de pénalité en termes de performances à simuler votre propre fonctionnalité de commutateur lors de la comparaison avec des chaînes. Le système ne va pas créer une table de saut O (1) comme il le peut avec des entiers, il va donc comparer chaque chaîne une à la fois de toute façon.

S'il y a de nombreux cas à comparer et que les performances sont un problème, l' List<T>option décrite ci-dessus peut être remplacée par un dictionnaire trié ou une table de hachage. Ensuite, les performances peuvent potentiellement correspondre ou dépasser l'option d'instruction switch.

Voici un exemple de la liste des délégués:

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

Bien sûr, vous souhaiterez probablement ajouter des paramètres standard et éventuellement un type de retour au délégué CustomSwitchDestination. Et vous voudrez vous faire de meilleurs noms!

Si le comportement de chacun de vos cas ne permet pas de déléguer l'invocation de cette manière, par exemple si des paramètres différents sont nécessaires, alors vous êtes coincé avec des ifinstructions chaînées . J'ai également fait cela plusieurs fois.

    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
la source
6
Sauf erreur de ma part, les deux ne sont différents que pour certaines cultures (comme le turc), et dans ce cas ne pourrait-il pas utiliser ToUpperInvariant()ou ToLowerInvariant()? De plus, il ne compare pas deux chaînes inconnues , il compare une chaîne inconnue à une chaîne connue. Ainsi, tant qu'il sait comment coder en dur la représentation en majuscules ou minuscules appropriée, le bloc de commutation devrait fonctionner correctement.
Seth Petry-Johnson
8
@Seth Petry-Johnson - Cette optimisation pourrait peut-être être faite, mais la raison pour laquelle les options de comparaison de chaînes sont intégrées dans le cadre est que nous n'avons pas tous à devenir des experts en linguistique pour écrire des logiciels corrects et extensibles.
Jeffrey L Whitledge
54
D'ACCORD. Je vais donner un exemple où c'est relivant. Supposons qu'au lieu de «house», nous ayons le mot (anglais!) «Café». Cette valeur peut être représentée aussi bien (et également probablement) par "caf \ u00E9" ou "cafe \ u0301". Égalité ordinale (comme dans une instruction switch) avec ToLower()ou ToLowerInvariant()renverra false. Equalsavec StringComparison.InvariantCultureIgnoreCaseretournera vrai. Étant donné que les deux séquences semblent identiques lorsqu'elles sont affichées, la ToLower()version est un bogue méchant à traquer. C'est pourquoi il est toujours préférable de faire des comparaisons de chaînes appropriées, même si vous n'êtes pas turc.
Jeffrey L Whitledge
77

Une approche plus simple consiste simplement à mettre votre chaîne en minuscules avant qu'elle n'entre dans l'instruction switch et à réduire la casse.

En fait, la partie supérieure est un peu meilleure du point de vue des performances de la nanoseconde extrême pure, mais moins naturelle à regarder.

Par exemple:

string s = "house"; 
switch (s.ToLower()) { 
  case "house": 
    s = "window"; 
    break;
}
Nick Craver
la source
1
Oui, je comprends que les minuscules sont un moyen, mais je veux que ce soit ignoreCase. Existe-t-il un moyen de remplacer l'instruction switch-case?
Tolsan le
6
@Lazarus - Ceci vient de CLR via C #, il a également été posté ici il y a quelque temps dans le fil des fonctionnalités cachées: stackoverflow.com/questions/9033/hidden-features-of-c/ ... Vous pouvez lancer LinqPad avec quelques millions d'itérations, c'est vrai.
Nick Craver
1
@Tolsan - Non, malheureusement, il n'y en a pas seulement à cause de sa nature statique. Il y a eu un bon lot de réponses à ce sujet il y a quelque temps: stackoverflow.com/questions/44905/…
Nick Craver
9
Il semble ToUpper(Invariant)être non seulement plus rapide, mais plus fiable: stackoverflow.com/a/2801521/67824
Ohad Schneider
47

Désolé pour ce nouveau message à une ancienne question, mais il existe une nouvelle option pour résoudre ce problème en utilisant C # 7 (VS 2017).

C # 7 propose désormais la «correspondance de modèles», et il peut être utilisé pour résoudre ce problème ainsi:

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

Cette solution traite également le problème mentionné dans la réponse de @Jeffrey L Whitledge selon laquelle la comparaison insensible à la casse des chaînes n'est pas la même chose que la comparaison de deux chaînes minuscules.

À propos, il y avait un article intéressant en février 2017 dans Visual Studio Magazine décrivant la correspondance de modèles et comment elle peut être utilisée dans les blocs de cas. Veuillez jeter un œil: Correspondance de motifs dans les blocs de cas C # 7.0

ÉDITER

À la lumière de la réponse de @ LewisM, il est important de souligner que la switchdéclaration a un nouveau comportement intéressant. Autrement dit, si votre caseinstruction contient une déclaration de variable, la valeur spécifiée dans la switchpartie est copiée dans la variable déclarée dans le case. Dans l'exemple suivant, la valeur trueest copiée dans la variable locale b. De plus, la variable best inutilisée et n'existe que pour que la whenclause de l' caseinstruction puisse exister:

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

Comme le souligne @LewisM, cela peut être utilisé pour en bénéficier - cet avantage étant que la chose comparée est en fait dans la switchdéclaration, comme c'est le cas avec l'utilisation classique de la switchdéclaration. En outre, les valeurs temporaires déclarées dans l' caseinstruction peuvent empêcher des modifications indésirables ou involontaires de la valeur d'origine:

switch(houseName)
{
    case string hn when hn.Equals("X", StringComparison.InvariantCultureIgnoreCase):
        windowName = "X-Window";
        break;
}
STLDev
la source
2
Ce serait plus long, mais je préférerais switch (houseName)alors faire la comparaison de la même manière que vous l'avez fait, c'estcase var name when name.Equals("MyHouse", ...
LewisM
@LewisM - C'est intéressant. Pouvez-vous en montrer un exemple concret?
STLDev
@LewisM - excellente réponse. J'ai ajouté une discussion supplémentaire sur l'attribution des switchvaleurs d'argument aux casevariables temporaires.
STLDev
Yay pour la correspondance de motifs en C # moderne
Thiago Silva
Vous pouvez également utiliser la «correspondance de modèle d'objet» comme ceci case { } whenpour ne pas avoir à vous soucier du type et du nom de la variable.
Bob le
32

Dans certains cas, il peut être judicieux d'utiliser une énumération. Alors commencez par analyser l'énumération (avec l'indicateur ignoreCase true) et ensuite avoir un commutateur sur l'énumération.

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
la source
Juste une note: Enum TryParse semble être disponible avec Framework 4.0 et versions ultérieures, FYI. msdn.microsoft.com/en-us/library/dd991317(v=vs.100).aspx
granadaCoder
4
Je préfère cette solution car elle décourage l'utilisation de cordes magiques.
user1069816
21

Une extension à la réponse de @STLDeveloperA. Une nouvelle façon de faire une évaluation d'instruction sans plusieurs instructions if à partir de c # 7 consiste à utiliser l'instruction Switch de correspondance de modèle, similaire à la façon dont @STLDeveloper, de cette manière, active la variable en cours de commutation.

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

Le magazine Visual Studio a un bel article sur les blocs de cas de correspondance de motifs qui pourraient valoir le coup d'œil.

LewisM
la source
Merci d'avoir souligné la fonctionnalité supplémentaire de la nouvelle switchdéclaration.
STLDev
5
+1 - cela devrait être la réponse acceptée pour le développement moderne (à partir de C # 7). Un changement que je ferais est que je case var name when "Bungalow".Equals(name, StringComparison.InvariantCultureIgnoreCase):coderais comme ceci: car cela peut empêcher une exception de référence nulle (où houseName est nul), ou encore ajouter un cas pour que la chaîne soit nulle en premier.
Jay
19

Une manière possible serait d'utiliser un dictionnaire d'ignorer les cas avec un délégué d'action.

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

dic["HouSe"]();

// Notez que l'appel ne renvoie pas de texte, mais ne remplit que la variable locale s.
// Si vous souhaitez renvoyer le texte réel, remplacez Actionpar Func<string>et les valeurs dans le dictionnaire par quelque chose comme() => "window2"

Magnus
la source
4
Plutôt que CurrentCultureIgnoreCase, OrdinalIgnoreCaseest préféré.
Richard Ev
2
@richardEverett préféré? Dépend de ce que vous voulez, si vous voulez que la culture actuelle ignore la casse, elle n'est pas préférée.
Magnus
Si quelqu'un est intéressé, ma solution (ci-dessous) prend cette idée et l'enveloppe dans une classe simple.
Flydog57
2

Voici une solution qui encapsule la solution de @Magnus dans une classe:

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

Voici un exemple d'utilisation dans une application Windows Form simple:

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

Si vous utilisez des lambdas (comme dans l'exemple), vous obtenez des fermetures qui captureront vos variables locales (assez proches du sentiment que vous obtenez d'une instruction switch).

Puisqu'il utilise un dictionnaire sous les couvertures, il obtient le comportement O (1) et ne se fie pas à parcourir la liste de chaînes. Bien sûr, vous devez construire ce dictionnaire, et cela coûte probablement plus cher.

Il serait probablement judicieux d'ajouter une bool ContainsCase(string aCase)méthode simple qui appelle simplement la ContainsKeyméthode du dictionnaire .

Flydog57
la source
1

J'espère que cela aide à essayer de convertir toute la chaîne en cas particulier en minuscules ou en majuscules et à utiliser la chaîne en minuscules à des fins de comparaison:

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();
    }
}
InconnuFellowCoder
la source
0

Cela devrait suffire:

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

La comparaison des commutateurs est donc invariante de culture. Pour autant que je sache, cela devrait aboutir au même résultat que les solutions de correspondance de motifs C # 7, mais de manière plus succincte.

Kevin Bennett
la source