Как заставить оператор C # Switch использовать IgnoreCase - PullRequest
65 голосов
/ 25 февраля 2010

Если у меня есть оператор switch-case, где объект в switch является строкой, возможно ли в любом случае выполнить ignoreCase сравнение?

У меня есть например:

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

Получит значение "окно". Как переопределить оператор switch-case, чтобы он сравнивал строки с помощью ignoreCase?

Ответы [ 8 ]

70 голосов
/ 25 февраля 2010

Более простой подход состоит в том, чтобы просто вставить строку в нижний регистр до того, как она войдет в оператор switch, и получить регистры ниже.

На самом деле, верхняя часть немного лучше с точки зрения чисто экстремальной наносекундной производительности, но выглядит менее естественно.

например:.

string s = "house"; 
switch (s.ToLower()) { 
  case "house": 
    s = "window"; 
    break;
}
56 голосов
/ 25 февраля 2010

Как вы, похоже, знаете, нижний регистр двух строк и сравнение их - это не то же самое, что сравнение без учета регистра. Есть много причин для этого. Например, стандарт Unicode позволяет кодировать текст с диакритическими знаками несколькими способами. Некоторые символы включают в себя как базовый, так и диакритический знак в одной кодовой точке. Эти символы также могут быть представлены как базовый символ, за которым следует объединенный диакритический знак. Эти два представления одинаковы для всех целей, и сопоставления строк с учетом культуры в .NET Framework правильно идентифицируют их как равные как с CurrentCulture, так и с InvariantCulture (с IgnoreCase или без него). Порядковое сравнение, с другой стороны, неверно расценивает их как неравные.

К сожалению, switch не делает ничего, кроме порядкового сравнения. Порядковое сравнение подходит для определенных видов приложений, таких как анализ файла ASCII со строго определенными кодами, но сравнение порядковых строк не подходит для большинства других применений.

То, что я делал в прошлом, чтобы получить правильное поведение, это просто макет моего собственного оператора switch. Есть много способов сделать это. Один из способов - создать List<T> пар строк и делегатов. Список можно искать, используя правильное сравнение строк. Когда совпадение найдено, может быть вызван связанный с ним делегат.

Другой вариант - создать очевидную цепочку if операторов. Обычно это оказывается не так плохо, как кажется, поскольку структура очень правильная.

Самое замечательное в этом то, что на самом деле нет никакой потери производительности при моделировании ваших собственных функций коммутатора при сравнении со строками. Система не собирается составлять таблицу переходов O (1) так, как это может быть с целыми числами, поэтому она все равно будет сравнивать каждую строку по одной за раз.

Если нужно сравнить множество случаев, и производительность является проблемой, то описанную выше опцию List<T> можно заменить отсортированным словарем или хэш-таблицей. Тогда производительность может потенциально соответствовать или превышать опцию оператора switch.

Вот пример списка делегатов:

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

Конечно, вы, вероятно, захотите добавить некоторые стандартные параметры и, возможно, тип возврата к делегату CustomSwitchDestination. И вы захотите сделать лучшие имена!

Если поведение каждого из ваших дел не поддается делегированию вызова таким образом, например, если необходимы разные параметры, то вы застряли с цепочечными if утверждениями. Я также делал это несколько раз.

    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";
    }
28 голосов
/ 30 мая 2011

В некоторых случаях было бы неплохо использовать перечисление. Поэтому сначала проанализируйте перечисление (с флагом ignoreCase true), а затем включите перечисление.

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;
   }
}
23 голосов
/ 09 июня 2017

Извините за этот новый пост на старый вопрос, но есть новая опция для решения этой проблемы с использованием C # 7 (VS 2017).

C # 7 теперь предлагает «сопоставление с образцом», и его можно использовать для решения этой проблемы следующим образом:

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

Это решение также касается проблемы, упомянутой в ответе @Jeffrey L Whitledge, что сравнение строк без учета регистра не то же самое, что сравнение двух строк в нижнем регистре.

Кстати, в феврале 2017 года в журнале Visual Studio была интересная статья, описывающая сопоставление с образцом и то, как его можно использовать в кейсах. Пожалуйста, посмотрите: Pattern Matching в C # 7.0 Case Blocks

EDIT

В свете ответа @ LewisM важно отметить, что оператор switch имеет новое, интересное поведение. То есть, если ваш оператор case содержит объявление переменной, то значение, указанное в части switch, копируется в переменную, объявленную в case. В следующем примере значение true копируется в локальную переменную b. Кроме того, переменная b не используется и существует только для того, чтобы могло существовать предложение when оператора case:

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

Как указывает @LewisM, это может быть использовано для получения выгоды - эта выгода заключается в том, что сравниваемая вещь на самом деле содержится в выражении switch, как и в классическом использовании выражения switch. Кроме того, временные значения, объявленные в операторе case, могут предотвратить нежелательные или непреднамеренные изменения исходного значения:

switch(houseName)
{
    case string hn when hn.Equals("X", StringComparison.InvariantCultureIgnoreCase):
        windowName = "X-Window";
        break;
}
16 голосов
/ 15 мая 2014

Один из возможных способов - использовать словарь игнорирования регистра с делегатом действия.

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

dic["HouSe"]();
11 голосов
/ 08 марта 2018

Расширение ответа от @STLDeveloperA. Новый способ оценки операторов без множественных операторов if с c # 7 - использование оператора Switch для сопоставления с образцом, аналогично тому, как @STLDeveloper, хотя этот способ включает переключаемую переменную

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

В журнале Visual Studio есть отличная статья о блоках для сопоставления с образцом , на которую стоит взглянуть.

2 голосов
/ 11 августа 2018

Вот решение, которое оборачивает решение @Magnus в класс:

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

Вот пример использования его в простом приложении Windows Form:

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

Если вы используете лямбду (как в примере), вы получите замыкания, которые будут фиксировать ваши локальные переменные (довольно близко к ощущению, которое вы получаете от оператора switch).

Так как он использует словарь под прикрытием, он получает поведение O (1) и не полагается на просмотр списка строк. Конечно, вам нужно создать этот словарь, и это, вероятно, стоит дороже.

Возможно, имеет смысл добавить простой метод bool ContainsCase(string aCase), который просто вызывает метод ContainsKey словаря.

1 голос
/ 22 мая 2015

Я надеюсь, что это поможет попытаться преобразовать всю строку в конкретный регистр, либо в нижний, либо в верхний регистр, и использовать для сравнения строчную строчную строку:

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