[英]Implementing pattern matching in C#
In Scala, you can use pattern matching to produce a result depending on the type of the input. 在Scala中,您可以使用模式匹配来生成结果,具体取决于输入的类型。 For instance:
例如:
val title = content match {
case blogPost: BlogPost => blogPost.blog.title + ": " + blogPost.title
case blog: Blog => blog.title
}
In C#, I'd ideally like to be able to write: 在C#中,我最好能够写:
var title = Visit(content,
(BlogPost blogPost) => blogPost.Blog.Title + ": " + blogPost.Title,
(Blog blog) => blog.Title
);
Is this possible? 这可能吗? When I've tried writing it as a single method, I don't know how to specify the generics.
当我尝试将其作为单一方法编写时,我不知道如何指定泛型。 The following implementation seems right, apart from getting the type checker to allow functions that accept subtypes of T:
以下实现似乎是正确的,除了让类型检查器允许接受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");
}
The closest I've gotten is to add the functions to an object individually, and then call visit on a value: 我最接近的是将函数单独添加到对象,然后调用访问值:
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;
}
}
This can be used like so: 这可以这样使用:
var title = new Visitor<IContent, string>()
.Add((BlogPost blogPost) => blogPost.Blog.Title + ": " + blogPost.Title)
.Add((Blog blog) => blog.Title)
.Visit(content);
Any idea how to do this with a single method call? 知道怎么用单个方法调用吗?
Pattern matching is one of those lovely features mostly found in functional programming languages like F#. 模式匹配是F#等函数式编程语言中最常见的功能之一。 There is a great project going on in codeplex named Functional C# .
在Codeplex中有一个名为Functional C#的伟大项目。 Consider the following F# code:
考虑以下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)
Using the Functional C# library, the C# equivalent would be: 使用Functional 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>();
Using Functional C# (from @Alireza) 使用Functional C#(来自@Alireza)
var title = content.Match()
.With<BlogPost>(blogPost => blogPost.Blog.Title + ": " + blogPost.Title)
.With<Blog>(blog => blog.Title)
.Result<string>();
In order to ensure total pattern matching, you would need to build the function into the type itself. 为了确保总模式匹配,您需要将该函数构建到类型本身中。 Here's how I'd do it:
这是我如何做到的:
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);
}
}
Check out my pattern matching implementation: repo 看看我的模式匹配实现: repo
It's based on expressions, so it offers equal perfomance with nested ifs. 它基于表达式,因此它提供了与嵌套ifs相同的性能。
Example usage: 用法示例:
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
Available throught NuGet: Nuget package 可通过NuGet: Nuget包获得
Generic implementation I'm using, that can match against the type, condition or a value: 我正在使用的通用实现,可以匹配类型,条件或值:
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;
}
}
}
And in your case, usage would look like: 在您的情况下,使用情况看起来像:
Match.With<IContent, string>(content)
.When<BlogPost>(blogPost => blogPost.Blog.Title)
.When<Blog>(blog => blog.Title)
.Result; // or just .Default(()=> "none");
Some other examples: 其他一些例子:
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);
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.