.NET 4 中的模式匹配

阅读数:1347 2009 年 5 月 30 日

话题:.NET函数式编程C#语言 & 开发架构

case 语句可以看作是 if/else 语法的特别版。他们的功能和作用是一样的,但有时 case 语句会令代码看起来更加清爽。考虑下面的 C# 和 VB 示例。

double CaclRateByDate(DayOfWeek day)
  {
      if (day == DayOfWeek.Monday)
      {
          return .42;
      }
      else if (day == DayOfWeek.Tuesday)
      {
          return .67;
      }
      else if (day == DayOfWeek.Wednesday)
      {
          return .56;
      }
      else if (day == DayOfWeek.Thursday)
      {
          return .34;
      }
      else if (day == DayOfWeek.Friday)
      {
          return .78;
      }
      else if (day == DayOfWeek.Saturday)
      {
          return .92;
      }
      else if (day == DayOfWeek.Sunday)
      {
          return .18;
      }
      throw new ArgumentOutOfRangeException("Unexpected enum value");
  }
Function CaclRateByDate(ByVal day As DayOfWeek) As Double
    If day = Monday Then
        Return 0.42
    ElseIf day = Tuesday Then
        Return 0.67
    ElseIf day = Wednesday Then
        Return 0.56
    ElseIf day = Thursday Then
        Return 0.34
    ElseIf day = Friday Then
        Return 0.78
    ElseIf day = Saturday Then
        Return 0.92
    ElseIf day = Sunday Then
        Return 0.18
    Else
        Throw New ArgumentOutOfRangeException("Unexpected enum value")
    End If
End Function

开发者需要一遍又一遍地编写“ElseIf day =”或“else if (day ==”这种语句,但却并没有增加任何信息。这种语句简直就是一种折磨,不停地分散开发者的注意力,我指的是 DayOfWeek 和返回值。

在 VB 和 C# 中,我们可以通过 case 语句进行简化。

double CaclRateByDate2(DayOfWeek day)
{
    switch (day)
    {
        case DayOfWeek.Monday:
            return .42;
        case DayOfWeek.Tuesday:
            return .67;
        case DayOfWeek.Wednesday:
            return .56;
        case DayOfWeek.Thursday:
            return .34;
        case DayOfWeek.Friday:
            return .78;
        case DayOfWeek.Saturday:
            return .92;
        case DayOfWeek.Sunday:
            return .18;
        default:
            throw new ArgumentOutOfRangeException("Unexpected enum value");
    }
}
Function CalcRateByDate2(ByVal day As DayOfWeek) As Double
    Select Case day
        Case Monday
            Return 0.42
        Case Tuesday
            Return 0.67
        Case Wednesday
            Return 0.56
        Case Thursday
            Return 0.34
        Case Friday
            Return 0.78
        Case Saturday
            Return 0.92
        Case Sunday
            Return 0.18
        Case Else
            Throw New ArgumentOutOfRangeException("Unexpected enum value")
    End Select
End Function

即便如此还是有不少的重复代码。为什么总是不断地说需要一个返回值呢?像下面这样写岂不更好?

double CaclRateByDate2(DayOfWeek day)
{
    return switch (day)
    {
        DayOfWeek.Monday: .42;
        DayOfWeek.Tuesday: .67;
        DayOfWeek.Wednesday: .56;
        DayOfWeek.Thursday: .34;
        DayOfWeek.Friday: .78;
        DayOfWeek.Saturday: .92;
        DayOfWeek.Sunday: .18;
        default:
            throw new ArgumentOutOfRangeException("Unexpected enum value");
    }
}
Function CalcRateByDate2(ByVal day As DayOfWeek) As Double
    Return Select Case day
        Monday: 0.42
        Tuesday: 0.67
        Wednesday: 0.56
        Thursday: 0.34
        Friday: 0.78
        Saturday: 0.92
        Sunday: 0.18
        Case Else
            Throw New ArgumentOutOfRangeException("Unexpected enum value")
    End Select
End Function

在消除了那些不必要的重复后,你会发现 C# 和 VB 代码看起来是如此的接近。剩下的代码就是寻找的模式以及与模式所匹配的结果了。这就是众所周知的模式匹配

遗憾的是,在 C# 4 和 VB 10 中并没有提供该特性,但却有一门新语言提供了对模式匹配的支持。看看下面这个由 Mathew Podwysocki编写的 F# 示例(需要说明的是,在下面这些示例中都创建了相应的函数)。

let calcRateByDay2 (day:System.DayOfWeek) = 
  match day with 
  | System.DayOfWeek.Monday -> 0.42 
  | System.DayOfWeek.Tuesday -> 0.67 
  | System.DayOfWeek.Wednesday -> 0.56 
  | System.DayOfWeek.Thursday -> 0.34 
  | System.DayOfWeek.Friday -> 0.78 
  | System.DayOfWeek.Saturday -> 0.92 
  | System.DayOfWeek.Sunday -> 0.18 
  | _ -> failwith "Unexpected enum value"

接下来 Mathew 又介绍了同时检查多个参数的方式。下面这个示例将下划线当作通配符。

let allowUrl url port =
  match (url, port) with
  | "http://www.microsoft.com/", 80 -> true
  | "http://example.com/", 8888 -> true
  | _, 80 -> true
  | _ -> false

遗憾的是,F# 的语法并不简洁。如果想要操纵某个值,那就不得不通过名称或占位符来指定了。

let calcRateByDay3 (day:System.DayOfWeek) = 
  match day with 
  | x when x >= System.DayOfWeek.Monday && x <= System.DayOfWeek.Friday -> 0.42
  | System.DayOfWeek.Saturday -> 0.92 
  | System.DayOfWeek.Sunday -> 0.18 
  | _ -> failwith "Unexpected enum value"

let calcRateByDay3 (day:System.DayOfWeek) = 
  match day with 
  | _ when day >= System.DayOfWeek.Monday && day <= System.DayOfWeek.Friday -> 0.42
  | System.DayOfWeek.Saturday -> 0.92 
  | System.DayOfWeek.Sunday -> 0.18 
  | _ -> failwith "Unexpected enum value"

下面的代码用 VB 的 case 语句实现同样的功能。

Function CaclRateByDate3(ByVal day As DayOfWeek) As Double
    Select Case day
        Case Monday To Friday : Return 0.42
        Case Saturday : Return 0.92
        Case Sunday : Return 0.18
        Case Else
            Throw New ArgumentOutOfRangeException("Unexpected enum value")
    End Select
End Function

如你所见,.NET 平台上的每种语言都有自己的一些语法优势,可以将他们应用到其他语言上而无需改变语言的核心。

查看英文原文:Pattern Matching in .NET 4