【发布时间】:2011-05-17 13:32:25
【问题描述】:
在 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
);
这可能吗?当我尝试将其编写为单个方法时,我不知道如何指定泛型。以下实现似乎是正确的,除了让类型检查器允许接受 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);
知道如何通过单个方法调用来做到这一点吗?
【问题讨论】:
-
有点像字典,键是类型,值是函数...
-
您使用的是 C# 3 还是 4?在 C# 4 中,Func 类型的形参类型是逆变的,这为您提供了更大的转换灵活性。
-
@Eric Lippert:在这种情况下,我认为我实际上想要协方差而不是逆变。我想接受可能无法接受 T 类型参数的函数(而您通常希望接受任何接受 T 类型参数的函数,其中包括接受 U 类型参数的函数,其中 T <: u>
-
@Michael:如果您想要委托类型的不安全协变,那么您可能会遇到一些困难。类型系统旨在帮助您防止此类事情,而不是帮助您做到这一点。
-
@Eric:类型安全是这段代码的原因:
visitor.Method.GetGenericArguments()[0].IsAssignableFrom(value.GetType())。我想允许可能不接受输入的函数,因为我想遍历传递的函数并从第一个接受输入的函数返回结果。
标签: c# pattern-matching