如何使C#Switch语句使用IgnoreCase


89

如果我有一个switch-case语句,其中开关中的对象是字符串,是否可以进行ignoreCase比较?

我有例如:

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

s获得值“窗口”吗?如何覆盖switch-case语句,以便它将使用ignoreCase比较字符串?

Answers:


63

如您所知,小写两个字符串并进行比较与进行忽略大小写比较不同。这有很多原因。例如,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";
    }

6
除非我没有记错,否则两者仅针对某些文化(例如土耳其语)有所不同,在那种情况下,他不能使用ToUpperInvariant()ToLowerInvariant()吗?此外,他没有比较两个未知字符串,而是将一个未知字符串与一个已知字符串进行了比较。因此,只要他知道如何对适当的大写或小写表示进行硬编码,那么切换块就可以正常工作。
塞斯·佩特里·约翰逊

8
@Seth Petry-Johnson-也许可以进行优化,但是将字符串比较选项引入框架的原因是,这样我们就不必成为语言专家来编写正确的可扩展软件。
Jeffrey L Whitledge,2010年

54
好。我将举一个例子,它是可靠的。假设我们有(英语!)单词“café”而不是“ house”。该值可以用“ caf \ u00E9”或“ cafe \ u0301”同样好(也很可能)表示。带有ToLower()或的序数相等性(如switch语句中一样)ToLowerInvariant()将返回false。EqualsStringComparison.InvariantCultureIgnoreCase将返回true。由于两个序列在​​显示时看起来相同,因此该ToLower()版本是一个令人讨厌的错误,无法跟踪。这就是为什么即使您不是土耳其人,也总是最好进行正确的字符串比较。
Jeffrey L Whitledge,2010年

77

一种更简单的方法是在将字符串放入switch语句之前将其小写,并减小大小写。

实际上,从纯粹的极端纳秒级性能的角度来看,鞋面要好一些,但看起来不太自然。

例如:

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

1
是的,我知道小写字母是一种方法,但是我希望它被忽略。有没有一种方法可以覆盖switch-case语句?
Tolsan

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
Ohad Schneider 2014年


47

抱歉,这个新帖子有一个老问题,但是有一个使用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年2月,在Visual Studio Magazine上有一篇有趣的文章描述了模式匹配以及如何在case块中使用它。请看一下:C#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;
}

2
它会更长一些,但是我希望switch (houseName)按照与您的方式类似的方式进行比较,例如case var name when name.Equals("MyHouse", ...
LewisM

@LewisM-很有意思。你能展示一个可行的例子吗?
STLDev

@LewisM-很好的答案。我已经添加了关于将switch参数值分配给case临时变量的进一步讨论。
STLDev

Yay进行现代C#模式匹配
Thiago Silva,

您也可以像这样使用“对象模式匹配”,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及更高版本,仅供参考。 msdn.microsoft.com/zh-CN/library/dd991317(v=vs.100).aspx
granadaCoder

4
我更喜欢这种解决方案,因为它不鼓励使用魔术弦。
user1069816 2014年

21

@STLDeveloperA对答案的扩展。一种新的执行语句评估的方法,从c#7开始不使用多个if语句,它使用模式匹配的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):因为这可以防止null引用异常(houseName为null),或者另选一个情况,使字符串优先为null。
周杰伦

19

一种可能的方法是使用带有动作委托的忽略大小写字典。

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

dic["HouSe"]();

//注意,该调用不会返回文本,而只会填充局部变量s。
//如果您想返回实际文本,请在字典中将和替换ActionFunc<string>() => "window2"


4
而不是CurrentCultureIgnoreCaseOrdinalIgnoreCase是首选。
理查德(Richard Ev)

2
@richardEverett首选?取决于您想要的内容,如果您希望当前的文化忽略大小写,则不是首选。
Magnus

如果有人感兴趣,我的解决方案(如下)将这个想法并包装在一个简单的类中。
Flydog57 '19

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窗体的应用程序中使用它的示例:

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

如果使用lambda(如示例所示),则会获得闭包,闭包将捕获局部变量(非常接近从switch语句获得的感觉)。

由于它在幕后使用了Dictionary,因此它具有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模式匹配解决方案相同的结果,但更为简洁。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.