2011-05-17 91 views
17

在Scala中,您可以使用模式匹配根據輸入的類型生成結果。例如:在C#中實現模式匹配

val title = content match { 
    case blogPost: BlogPost => blogPost.blog.title + ": " + blogPost.title 
    case blog: Blog => blog.title 
} 

在C#中,我非常希望能夠寫:

var title = Visit(content, 
    (BlogPost blogPost) => blogPost.Blog.Title + ": " + blogPost.Title, 
    (Blog blog) => blog.Title 
); 

這可能嗎?當我嘗試將其作爲單一方法編寫時,我不知道如何指定泛型。下面的實現似乎是正確的,除了讓類型檢查,以允許接受的T亞型功能:

public TResult Visit<T, TResult>(T value, params Func<T, TResult>[] visitors) 
    { 
     foreach (var visitor in visitors) 
     { 
      if (visitor.Method.GetGenericArguments()[0].IsAssignableFrom(value.GetType())) 
      { 
       return visitor(value); 
      } 
     } 
     throw new ApplicationException("No match"); 
    } 

我已經得到最接近的是將功能添加到一個單獨的對象,然後調用參觀一值:

public class Visitor<T, TResult> 
    { 
     private class Result 
     { 
      public bool HasResult; 
      public TResult ResultValue; 
     } 

     private readonly IList<Func<T, Result>> m_Visitors = new List<Func<T, Result>>(); 

     public TResult Visit(T value) 
     { 
      foreach (var visitor in m_Visitors) 
      { 
       var result = visitor(value); 
       if (result.HasResult) 
       { 
        return result.ResultValue; 
       } 
      } 
      throw new ApplicationException("No match"); 
     } 

     public Visitor<T, TResult> Add<TIn>(Func<TIn, TResult> visitor) where TIn : T 
     { 
      m_Visitors.Add(value => 
      { 
       if (value is TIn) 
       { 
        return new Result { HasResult = true, ResultValue = visitor((TIn)value) }; 
       } 
       return new Result { HasResult = false }; 
      }); 
      return this; 
     } 
    } 

這可以用於像這樣:

var title = new Visitor<IContent, string>() 
    .Add((BlogPost blogPost) => blogPost.Blog.Title + ": " + blogPost.Title) 
    .Add((Blog blog) => blog.Title) 
    .Visit(content); 

不知道如何用一個方法調用做到這一點?

+2

還挺看起來像一本字典,其中的關鍵是類型和值是一個函數... – Roly 2011-05-17 13:43:47

+1

您正在使用C#3或4?在C#4中,Func類型在其正式參數類型中是相反的,這爲您提供了更多的轉換靈活性。 – 2011-05-17 14:09:10

+0

@Eric Lippert:在這種情況下,我*想*我實際上想要協變而不是反轉。我想接受可能無法接受類型T參數的函數(而您通常會接受任何接受類型T參數的函數,其中包含接受U類參數的函數,其中T <:U) – 2011-05-17 14:20:36

回答

9

使用功能性C#(從@Alireza)

var title = content.Match() 
    .With<BlogPost>(blogPost => blogPost.Blog.Title + ": " + blogPost.Title) 
    .With<Blog>(blog => blog.Title) 
    .Result<string>(); 
+0

功能C#似乎使用我使用過的方法也就是將每個lambda傳遞給不同的方法調用,似乎表明在單個方法調用中完成所有操作都是不可能的(至少在維護類型安全的同時)。呃,好吧。 – 2011-05-19 21:36:38

14

模式匹配是大多數在F#等函數式編程語言中可以找到的可愛特徵之一。有一個名爲Functional C#的codeplex正在進行一個偉大的項目。 考慮下面的F#的代碼:

let operator x = match x with 
       | ExpressionType.Add -> "+" 

let rec toString exp = match exp with 
         | LambdaExpression(args, body) -> toString(body) 
         | ParameterExpression(name) -> name 
         | BinaryExpression(op,l,r) -> sprintf "%s %s %s" (toString l) (operator op) (toString r) 

使用功能C#庫中,C#當量將是:

var Op = new Dictionary<ExpressionType, string> { { ExpressionType.Add, "+" } }; 

Expression<Func<int,int,int>> add = (x,y) => x + y; 

Func<Expression, string> toString = null; 
toString = exp => 
exp.Match() 
    .With<LambdaExpression>(l => toString(l.Body)) 
    .With<ParameterExpression>(p => p.Name) 
    .With<BinaryExpression>(b => String.Format("{0} {1} {2}", toString(b.Left), Op[b.NodeType], toString(b.Right))) 
    .Return<string>(); 
5

爲了爲了確保總體模式匹配,您需要將該函數構建到類型本身中。以下是我想做到這一點:

public abstract class Content 
{ 
    private Content() { } 

    public abstract T Match<T>(Func<Blog, T> convertBlog, Func<BlogPost, T> convertPost); 

    public class Blog : Content 
    { 
     public Blog(string title) 
     { 
      Title = title; 
     } 
     public string Title { get; private set; } 

     public override T Match<T>(Func<Blog, T> convertBlog, Func<BlogPost, T> convertPost) 
     { 
      return convertBlog(this); 
     } 
    } 

    public class BlogPost : Content 
    { 
     public BlogPost(string title, Blog blog) 
     { 
      Title = title; 
      Blog = blog; 
     } 
     public string Title { get; private set; } 
     public Blog Blog { get; private set; } 

     public override T Match<T>(Func<Blog, T> convertBlog, Func<BlogPost, T> convertPost) 
     { 
      return convertPost(this); 
     } 
    } 

} 

public static class Example 
{ 
    public static string GetTitle(Content content) 
    { 
     return content.Match(blog => blog.Title, post => post.Blog.Title + ": " + post.Title); 
    } 
} 
+0

這實質上是訪問者模式的實現,除了使用兩個方法傳遞兩個lambda表達式的類之外。 – 2016-02-27 18:11:22

1

看看我的模式匹配的實現:repo

它是基於表達式,因此它提供了平等的性能比較嵌套IFS。

實例:

string s1 = "Hello"; 
string s2 = null; 

Func<Option<string>> match = new Matcher<Option<string>> 
{ 
    {s => s is None, s => Console.WriteLine("None")}, 
    {s => s is Some, s => Console.WriteLine((string)s) // or s.Value 
}; 

match(s1); // Hello 
match(s2); // None 

推介throught的NuGet:Nuget package

+0

請發佈必要的代碼片段。 – 2014-04-23 08:32:53

0

通用執行我使用,可以匹配針對的類型,狀態或值:

public static class Match 
{ 
    public static PatternMatch<T, R> With<T, R>(T value) 
    { 
     return new PatternMatch<T, R>(value); 
    } 

    public struct PatternMatch<T, R> 
    { 
     private readonly T _value; 
     private R _result; 

     private bool _matched; 

     public PatternMatch(T value) 
     { 
      _value = value; 
      _matched = false; 
      _result = default(R); 
     } 

     public PatternMatch<T, R> When(Func<T, bool> condition, Func<R> action) 
     { 
      if (!_matched && condition(_value)) 
      { 
       _result = action(); 
       _matched = true; 
      } 

      return this; 
     } 

     public PatternMatch<T, R> When<C>(Func<C, R> action) 
     { 
      if (!_matched && _value is C) 
      { 
       _result = action((C)(object)_value); 
       _matched = true; 
      } 
      return this; 
     } 


     public PatternMatch<T, R> When<C>(C value, Func<R> action) 
     { 
      if (!_matched && value.Equals(_value)) 
      { 
       _result = action(); 
       _matched = true; 
      } 
      return this; 
     } 


     public R Result => _result; 

     public R Default(Func<R> action) 
     { 
      return !_matched ? action() : _result; 
     } 
    } 
} 

而在你的情況下,使用看起來像:

Match.With<IContent, string>(content) 
    .When<BlogPost>(blogPost => blogPost.Blog.Title) 
    .When<Blog>(blog => blog.Title) 
    .Result; // or just .Default(()=> "none"); 

一些其他的例子:

var result = Match.With<IFoo, int>(new Foo() { A = 5 }) 
    .When<IFoo>(foo => foo.A) 
    .When<IBar>(bar => bar.B) 
    .When<string>(Convert.ToInt32) 
    .Result; 
Assert.Equal(5, result); 

var result = Match.With<int, string>(n) 
    .When(x => x > 100,() => "n>100") 
    .When(x => x > 10,() => "n>10") 
    .Default(() => ""); 
Assert.Equal("n>10", result); 

var result = Match.With<int, string>(5) 
    .When(1,() => "1") 
    .When(5,() => "5") 
    .Default(() => "e"); 
Assert.Equal("5", result);