Как сделать выражение оператора С# использовать IgnoreCase
Если у меня есть оператор switch-case, где объект в коммутаторе является строкой, можно ли в любом случае игнорировать сравнение Case?
Я имею, например:
string s = "house";
switch (s)
{
case "houSe": s = "window";
}
Получит значение "окно". Как переопределить оператор switch-case, чтобы он сравнивал строки с помощью ignoreCase?
Ответы
Ответ 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();
}
}