Як зробити так, щоб у заяві комутатора C # використовувався IgnoreCase


89

Якщо у мене є оператор switch-case, де об’єктом у комутаторі є рядок, чи можна зробити порівняння ignoreCase?

У мене є, наприклад:

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

Отримає sзначення "вікно"? Як я можу замінити оператор switch-case, щоб він порівняв рядки за допомогою ignoreCase?

Відповіді:


63

Як ви, мабуть, знаєте, зниження двох рядків та їх порівняння - це не те саме, що робити порівняння з ігноруванням регістру. Для цього існує маса причин. Наприклад, стандарт Unicode дозволяє кодувати текст із діакритикою кількома способами. Деякі символи включають як базовий символ, так і діакритичний знак в одній кодовій точці. Ці символи також можуть бути представлені як базовий символ, за яким слідує комбінуючий діакритичний символ. Ці два подання є рівними для всіх цілей, і порівняння рядків з урахуванням культури в .NET Framework правильно визначить їх рівними або з CurrentCulture, або з InvariantCulture (з IgnoreCase або без нього). Порядкове порівняння, навпаки, неправильно вважатиме їх нерівними.

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

Те, що я робив раніше, щоб отримати правильну поведінку, - це просто макет мого власного висловлення перемикача. Існує безліч способів зробити це. Одним із способів було б створити List<T>пару пар рядків справи та делегатів. Шукати у списку можна за допомогою відповідного порівняння рядків. Коли збіг буде знайдено, тоді може бути викликаний пов'язаний делегат.

Інший варіант - зробити очевидний ланцюжок ifтверджень. Зазвичай це виявляється не так погано, як це звучить, оскільки структура дуже регулярна.

Чудовим у цьому є те, що насправді немає покарання за продуктивність у знущанні над власною функцією комутатора при порівнянні з рядками. Система не збирається створювати таблицю стрибків O (1) так, як це можна з цілими числами, тому вона все одно буде порівнювати кожен рядок по одному.

Якщо існує багато випадків для порівняння, і продуктивність є проблемою, тоді List<T>описаний вище варіант можна замінити відсортованим словником або хеш-таблицею. Тоді продуктивність може потенційно збігатися або перевищувати опцію оператора перемикання.

Ось приклад списку делегатів:

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

6
Якщо я не помиляюся, ці два лише відрізняються для певних культур (наприклад, турецької), і в такому випадку він не міг використати ToUpperInvariant()чи ToLowerInvariant()? Крім того, він не порівнює два невідомі рядки , він порівнює один невідомий рядок з одним відомим рядком. Таким чином, до тих пір, поки він знає, як правильно кодувати відповідне подання верхнього чи нижнього регістру, тоді блок перемикачів повинен працювати нормально.
Сет Петрі-Джонсон,

8
@ Сет Петрі-Джонсон - Можливо, таку оптимізацію можна було б зробити, але причиною того, що параметри порівняння рядків вписані у фреймворк, є те, що нам не всім потрібно стати експертами з лінгвістики, щоб писати правильне, розширюване програмне забезпечення.
Jeffrey L Whitledge

54
В ПОРЯДКУ. Я наведу приклад, коли це релівантно. Припустимо, замість "будинок" у нас було (англійське!) Слово "кафе". Це значення могло б бути представлене однаково добре (і однаково ймовірно) або "caf \ u00E9", або "cafe \ u0301". Порядкова рівність (як у операторі switch) з ToLower()або ToLowerInvariant()поверне false. Equalsз StringComparison.InvariantCultureIgnoreCaseповернеться правдою. Оскільки обидві послідовності виглядають однаково при відображенні, ToLower()версія є неприємною помилкою, яку можна відстежити. Ось чому завжди краще робити належне порівняння рядків, навіть якщо ви не турець.
Jeffrey L Whitledge

77

Більш простий підхід - це просто зменшити кількість рядків до того, як він перейде до оператора switch, а регістри зменшити.

Насправді верхня частина трохи краща з точки зору чистої екстремальної наносекунди, але на це менш природно.

Наприклад:

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

1
Так, я розумію, що використання нижчих літер - це спосіб, але я хочу, щоб це було ignoreCase. Чи є спосіб, яким я можу перевизначити оператор switch-case?
Толсан

6
@Lazarus - Це від CLR через C #, він також був опублікований тут ще в потоці прихованих функцій: stackoverflow.com/questions/9033/hidden-features-of-c/ ... Ви можете запустити LinqPad з кількома мільйон ітерацій, справді.
Нік Кравер

1
@Tolsan - Ні, на жаль, існує не лише через це статичність. Деякий час тому було багато відповідей на це: stackoverflow.com/questions/44905/…
Нік Кравер

9
Здається, ToUpper(Invariant)це не тільки швидше, але і надійніше: stackoverflow.com/a/2801521/67824
Охад Шнайдер


48

Вибачте за цей новий допис на старе питання, але є новий варіант вирішення цієї проблеми за допомогою 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 Magazine була цікава стаття, в якій описувалось узгодження шаблонів та те, як його можна використовувати у блоках регістрів. Будь ласка, подивіться: Зіставлення зразків у блоках справи C # 7.0

РЕДАГУВАТИ

У світлі відповіді @ 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;
}

2
Це було б довше, але я волів би switch (houseName)тоді зробити порівняння, подібне тому, як ви це робили, тобтоcase var name when name.Equals("MyHouse", ...
ЛьюїсМ

@LewisM - Це цікаво. Чи можете ви показати діючий приклад цього?
STLDev

@LewisM - чудова відповідь. Я додав подальше обговорення щодо присвоєння switchзначень аргументів caseтимчасовим змінним.
STLDev

Yay за узгодження зразків у сучасному C #
Тьяго Сільва

Ви також можете використовувати "узгодження шаблону об'єкта", наприклад, щоб case { } whenне турбуватися про тип змінної та ім'я.
Боб

32

У деяких випадках корисно використовувати перерахування. Отже, спочатку проаналізуйте перерахування (із позначкою 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;
   }
}

Тільки примітка: Enum TryParse, здається, доступний з Framework 4.0 і вперед, FYI. msdn.microsoft.com/en-us/library/dd991317(v=vs.100).aspx
granadaCoder

4
Я віддаю перевагу цьому рішенню, оскільки воно не рекомендує використовувати магічні струни.
user1069816

22

Розширення відповіді @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;
}

Журнал «Візуальна студія» має приємну статтю про блоки збігу візерунків, які, можливо, варто переглянути.


Дякуємо, що вказали на додаткову функціональність нової switchзаяви.
STLDev

5
+1 - це має бути прийнятою відповіддю для сучасного розвитку (від C # 7 і далі). Однією зміною, яку я б зробив, є те, що я б case var name when "Bungalow".Equals(name, StringComparison.InvariantCultureIgnoreCase):кодував так: оскільки це може запобігти нульовому виняткові посилання (де houseName має значення null), або ж додати регістр для рядка, який спочатку є нульовим.
Джей,

19

Одним із можливих способів було б використання словника ігнорування регістрів із делегатом дії.

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

dic["HouSe"]();

// Зверніть увагу, що виклик не повертає текст, а лише заповнює локальну змінну s.
// Якщо ви хочете повернути реальний текст, замінити Actionна Func<string>і значення в словнику , щоб що - щось на зразок() => "window2"


4
Замість CurrentCultureIgnoreCase, OrdinalIgnoreCaseє кращим.
Richard Ev,

2
@richardEverett Вподобаний? Залежно від того, що ви хочете, якщо ви хочете, щоб поточна культура ігнорувала регістр, це не є кращим.
Магнус,

Якщо комусь цікаво, моє рішення (нижче) бере цю ідею і обертає її у простий клас.
Flydog57,

2

Ось рішення, яке обгортає рішення @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

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

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

0

Для цього має бути достатньо:

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

Таким чином, порівняння перемикачів є інваріантом культури. Наскільки я бачу, це повинно досягти того самого результату, що і рішення з узгодження шаблонів C # 7, але більш стисло.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.