Подтвердить что ты не робот

Как сделать выражение оператора С# использовать IgnoreCase

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

Я имею, например:

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

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

4b9b3361

Ответ 1

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

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

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

Другой вариант - сделать очевидную цепочку операторов 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";
    }

Ответ 2

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

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

например:.

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

Ответ 3

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

Ответ 4

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

С# 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 в С# 7.0 Case Blocks

РЕДАКТИРОВАТЬ

В свете ответа @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;
}

Ответ 5

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

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

dic["HouSe"]();

Ответ 6

Расширение ответа от @STLDeveloperA. Новый способ оценки операторов без множественных операторов if с С# 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 есть хорошая статья о блоках, соответствующих шаблонам, которые стоит посмотреть.

Ответ 7

Вот решение, которое оборачивает решение @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 словаря.

Ответ 8

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

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