在C#中实现模式匹配

在Scala中,您可以使用模式匹配来生成结果,具体取决于输入的类型。 例如:

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

这可能吗? 当我尝试将其作为单一方法编写时,我不知道如何指定generics。 以下实现似乎是正确的,除了让类型检查器允许接受T的子类型的函数:

  public TResult Visit(T value, params Func[] 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 { private class Result { public bool HasResult; public TResult ResultValue; } private readonly IList<Func> m_Visitors = new List<Func>(); 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 Add(Func 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() .Add((BlogPost blogPost) => blogPost.Blog.Title + ": " + blogPost.Title) .Add((Blog blog) => blog.Title) .Visit(content); 

知道怎么用单个方法调用吗?

使用Functional C#(来自@Alireza)

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

模式匹配是F#等函数式编程语言中最常见的function之一。 在Codeplex中有一个名为Functional C#的伟大项目。 考虑以下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) 

使用Functional C#库,C#等价物将是:

 var Op = new Dictionary { { ExpressionType.Add, "+" } }; Expression> add = (x,y) => x + y; Func toString = null; toString = exp => exp.Match() .With(l => toString(l.Body)) .With(p => p.Name) .With(b => String.Format("{0} {1} {2}", toString(b.Left), Op[b.NodeType], toString(b.Right))) .Return(); 

为了确保总模式匹配,您需要将函数构建到类型本身中。 我是这样做的:

 public abstract class Content { private Content() { } public abstract T Match(Func convertBlog, Func convertPost); public class Blog : Content { public Blog(string title) { Title = title; } public string Title { get; private set; } public override T Match(Func convertBlog, Func 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(Func convertBlog, Func 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); } } 

看看我的模式匹配实现: repo

它基于表达式,因此它提供了与嵌套ifs相同的性能。

用法示例:

 string s1 = "Hello"; string s2 = null; Func> match = new Matcher> { {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 

可通过NuGet: Nuget包获得

我正在使用的通用实现,可以匹配类型,条件或值:

 public static class Match { public static PatternMatch With(T value) { return new PatternMatch(value); } public struct PatternMatch { private readonly T _value; private R _result; private bool _matched; public PatternMatch(T value) { _value = value; _matched = false; _result = default(R); } public PatternMatch When(Func condition, Func action) { if (!_matched && condition(_value)) { _result = action(); _matched = true; } return this; } public PatternMatch When(Func action) { if (!_matched && _value is C) { _result = action((C)(object)_value); _matched = true; } return this; } public PatternMatch When(C value, Func action) { if (!_matched && value.Equals(_value)) { _result = action(); _matched = true; } return this; } public R Result => _result; public R Default(Func action) { return !_matched ? action() : _result; } } } 

在您的情况下,使用情况看起来像:

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

其他一些例子:

 var result = Match.With(new Foo() { A = 5 }) .When(foo => foo.A) .When(bar => bar.B) .When(Convert.ToInt32) .Result; Assert.Equal(5, result); var result = Match.With(n) .When(x => x > 100, () => "n>100") .When(x => x > 10, () => "n>10") .Default(() => ""); Assert.Equal("n>10", result); var result = Match.With(5) .When(1, () => "1") .When(5, () => "5") .Default(() => "e"); Assert.Equal("5", result);