有没有办法在c#中打开部分字符串?

时间:2014-02-26 11:00:43

标签: c# switch-statement pattern-matching

我有一个我需要存储在数据库中的不同类型图像的列表,它们都有类型描述,例如IndoorGardenSummer等等,但有很多包含重复单词的说明,例如GardenSummerAreaSummer1KM都包含“Summer”,那么我有办法在c#中执行类似的操作:

open System

let strs = ["Kitchen"; "GardenSummer"; "GardenWinter"; "AreaSummer1KM"; "PoolIndoors"; "LivingRoom"; "BathRoom"]

let switch (x: string) = match x with
    | a when a.Contains "Summer"    -> Some "Summer"  // here
    | b when b.Contains "Winter"    -> Some "Winter"  // here
    | "Exterior" | "ParkFacilities" -> Some "Outdoors"
    | "Kitchen"  | "Landing"        -> Some "Indoors"
    | c when c.Contains "Room"      -> Some "Indoors" // and here
    | _                             -> None


let sorted = List.map switch strs

// part from here and down was just added to print the contents, and isn't a part of the issue
let printOption = function
    | Some v -> v.ToString () |> Console.WriteLine
    | None   -> "No Match"    |> Console.WriteLine

List.iter printOption sorted

有没有办法让我在不制作一堆else if的情况下打开str.Contains(str2)?

1 个答案:

答案 0 :(得分:3)

简短的回答是否定的。

稍微长一点的答案是“不,有充分理由”。 switch语句实际上是一个非常聪明的语句,在许多情况下表现优于if - else if语句链(典型的switch (MessageType) ...是一个很好的例子)。但是,要做到这一点,它需要保留某些合同。最后,它不会评估每种可能性。它执行类似于可能选项的二进制搜索。

最后,您的F#代码可能与if - else if语句相当,而不是相当于C#中的switch

当然,没有什么能阻止你创建自己的方法,这种方法在语法上类似于F#的match。匿名委托,泛型函数,所有这些使得编写这样的语法缩写器相当容易:)

当然,还有其他选择,比如使用正则表达式等。如果搜索到的字符串很长,连续10次调用Contains将意味着显着的性能损失。

您的数据和交换机的一些示例正则表达式。常用代码如下:

void Main()
{
    var data = 
    new [] 
    {
      "Kitchen", "GardenSummer", "GardenWinter", "AreaSummer1KM", 
      "PoolIndoors", "LivingRoom", "BathRoom", "Exterior", "ParkFacilities"
    };

    foreach (var str in data)
    {
      Matcher(str).Dump();
    }
}

现在我们要改变的是Matcher方法实现。

首先,简化整个过程并避免多个字符串匹配(比较字符串并不完全免费):

Regex matcherRegex = new Regex("(Summer)|(Winter)|(^Exterior|ParkFacilities$)", 
                               RegexOptions.Compiled);
string Matcher(string input)
{
  var m = matcherRegex.Match(input);

  if (m.Groups.Count == 4)
  {
    if (m.Groups[0].Success) return "Summer";
    else if (m.Groups[1].Success) return "Winter";
    else if (m.Groups[2].Success) return "Outdoors";
  }

  return null;
}

所以,我们仍然有一个if-else链,但我们不再多次遍历字符串。它还允许您轻松指定所需的条件。

通过使用LINQ,一种方法可以将其提升为“转换”。出于性能原因,这绝对不是你想要做的事情,它只是关于美学:

var groupIndex = m.Groups.OfType<Group>()
                         .Skip(1)
                         .Select((i, idx) => new { Item = i, Index = idx + 1 })
                         .Where(i => i.Item.Success)
                         .Select(i => i.Index)
                         .FirstOrDefault();
switch (groupIndex)
{
  case 0: return null;
  case 1: return "Summer";
  case 2: return "Winter";
  case 3: return "Outdoors";
}

基本上,我得到匹配组的索引,然后使用开关。正如我之前所说,这可能比第一个变量慢,至少是由于LINQ开销。

您还可以使用命名捕获来按名称获取匹配的组,而不是通过索引来获取,这更易于维护。此外,对于简单的情况,您可以使用命名的组名来完全避免切换:

Regex matcherRegex = 
  new Regex("(?<Summer>Summer)"
            + "|(?<Winter>Winter)"
            + "|(?<Outdoors>(^Exterior|ParkFacilities$))", 
            RegexOptions.Compiled | RegexOptions.ExplicitCapture);

string Matcher(string input)
{
  return matcherRegex.Match(input)
         .Groups
         .OfType<Group>()
         .Select((i, idx) => new { Item = i, Index = idx })
         .Skip(1)
         .Where(i => i.Item.Success)
         .Select(i => matcherRegex.GroupNameFromNumber(i.Index))
         .FirstOrDefault();
}

所有这些只是样本,你可能想要更改那些以获得更好的边缘情况或异常处理和性能,但它显示了这些想法。

最后一个版本特别方便,因为没有什么可以阻止你使用它作为一个常用方法来处理你可以在正则表达式中解释的所有字符串“开关”。遗憾的是,组名允许很多unicode字符,但不允许有空格;但是,你无法解决这个问题。

你甚至可以自动构建模式匹配器,例如将Expression<Func<...>>传递给辅助方法,但这会进入复杂的领域:)