web-dev-qa-db-fra.com

Comment faire pour que l'instruction C # Switch utilise IgnoreCase

Si j'ai une instruction switch-case où l'objet dans le commutateur est une chaîne, est-il possible de faire ignorer la comparaison de toute façon?

J'ai par exemple:

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

Obtiendra la valeur "fenêtre". Comment remplacer l'instruction switch-case pour qu'elle compare les chaînes en utilisant ignoreCase?

68
Tolsan

Comme vous semblez le savoir, mettre en minuscules deux chaînes et les comparer n'est pas la même chose que faire une comparaison de casse ignorée. Il y a beaucoup de raisons à cela. Par exemple, la norme Unicode permet au texte avec des signes diacritiques d'être codé de plusieurs façons. Certains caractères incluent à la fois le caractère de base et le 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 combinant. Ces deux représentations sont égales à toutes fins utiles, et les comparaisons de chaînes sensibles à la culture dans .NET Framework les identifieront correctement comme égales, avec CurrentCulture ou InvariantCulture (avec ou sans IgnoreCase). Une comparaison ordinale, d'autre part, les considérera à tort comme inégales.

Malheureusement, switch ne 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 est juste de simuler ma propre instruction switch. Il y a plusieurs manières de faire ça. Une façon serait de créer un List<T> de paires 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 des instructions if. Cela s'avère généralement moins mauvais 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é de performance dans la simulation de votre propre fonctionnalité de commutateur lors de la comparaison avec des chaînes. Le système ne va pas créer une table de sauts O(1)) comme il peut avec des entiers, donc il va de toute façon comparer chaque chaîne une par une.

S'il existe de nombreux cas à comparer et que les performances posent problème, le List<T> L'option décrite ci-dessus pourrait ê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 de commutateur.

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 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, comme si des paramètres différents sont nécessaires, alors vous êtes bloqué avec des instructions chaînées if. Je l'ai aussi fait 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";
    }
56
Jeffrey L Whitledge

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

En fait, la tige est un peu meilleure du point de vue de la performance en nanosecondes extrêmes, mais moins naturelle à regarder.

Par exemple.:

string s = "house"; 
switch (s.ToLower()) { 
  case "house": 
    s = "window"; 
    break;
}
70
Nick Craver

Dans certains cas, il peut être judicieux d'utiliser une énumération. Donc, commencez par analyser l'énumération (avec le drapeau 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;
   }
}
29
uli78

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 motifs", et il peut être utilisé pour résoudre ce problème de la manière suivante:

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 du problème mentionné dans la réponse de @Jeffrey L Whitledge selon lequel la comparaison insensible à la casse des chaînes n'est pas la même chose que la comparaison de deux chaînes en bas de casse.

Soit dit en passant, 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 des blocs de cas. Veuillez jeter un coup d'œil: Correspondance de motifs dans les blocs de cas C # 7.

[~ # ~] modifier [~ # ~]

À la lumière de la réponse de @ LewisM, il est important de souligner que l'instruction switch a un nouveau comportement intéressant. En d'autres termes, si votre instruction case contient une déclaration de variable, la valeur spécifiée dans la partie switch est copiée dans la variable déclarée dans le case. Dans l'exemple suivant, la valeur true est copiée dans la variable locale b. De plus, la variable b n'est pas utilisée et existe uniquement pour que la clause when de l'instruction case 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 bénéficier - cet avantage étant que la chose comparée se trouve en fait dans l'instruction switch, comme c'est le cas avec l'utilisation classique de l'instruction switch. De plus, les valeurs temporaires déclarées dans l'instruction case 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;
}
26
STLDev

Une façon possible serait d'utiliser un dictionnaire de cas ignoré 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"]();
16
Magnus

Une extension de la réponse de @STLDeveloperA. Une nouvelle façon de faire une évaluation d'instruction sans multiple si les instructions à partir de c # 7 utilisent l'instruction Switch de correspondance de modèle, similaire à la façon dont @STLDeveloper bien que 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 Nice article on pattern matching case blocks qui pourrait valoir le coup d'œil.

12
LewisM

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 de son 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 de l'impression que vous obtenez d'une instruction switch).

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

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

2
Flydog57

J'espère que cela aide à essayer de convertir la chaîne entière en minuscules ou en majuscules et d'utiliser la chaîne en minuscules pour la 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();
    }
}
1