Как сделать оператор C# Switch использовать IgnoreCase



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



У меня например:



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


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

723   8  

8 ответов:

как вы, кажется, знаете, в нижнем регистре две строки и сравнение их не то же самое, что делать сравнение игнорирования. Для этого есть много причин. Например, стандарт Unicode позволяет кодировать текст с диакритическими знаками несколькими способами. Некоторые символы включают в себя как базовый символ, так и диакритический в одной кодовой точке. Эти символы также могут быть представлены в виде базового символа, за которым следует комбинированный диакритический символ. Эти два представления равны для все цели и сравнения строк с учетом языка и региональных параметров в .NET Framework будут правильно идентифицировать их как равные либо с CurrentCulture, либо с инвариантной культурой (с 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 statments. Я тоже делал это несколько раз.

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

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

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

например:

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

в некоторых случаях это может быть хорошей идеей, чтобы использовать перечислимый. Поэтому сначала проанализируйте перечисление (с флагом 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;
   }
}

извините за этот новый пост на старый вопрос, но есть новый вариант решения этой проблемы с помощью 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, описывающая сопоставление шаблонов и как его можно использовать в блоках case. Пожалуйста, посмотрите:сопоставление шаблонов в блоках C# 7.0 Case

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

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

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

dic["HouSe"]();

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

string houseName = "house";  // value to be tested
string s;
switch (houseName)
{
    case var name when name.Equals("Bungalow", StringComparison.InvariantCultureIgnoreCase): 
        s = "Single glazed";
    break;

    case var name when name.Equals("Church", StringComparison.InvariantCultureIgnoreCase);
        s = "Stained glass";
        break;
        ...
    default:
        s = "No windows (cold or dark)"
        break;
}

журнал visual studio имеет хорошая статья о сопоставлении с образцом case blocks что может быть стоит посмотреть.

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

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

вот решение, которое обертывает решение @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:

   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 метод.

Comments

    Ничего не найдено.