2011-05-17 14 views
17

En Scala, puede utilizar coincidencia de patrones para producir un resultado en función del tipo de la entrada. Por ejemplo:La implementación de coincidencia de patrones en C#

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

En C#, me gusta lo ideal será capaz de escribir:

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

Es esto posible? Cuando intenté escribirlo como un método único, no sé cómo especificar los genéricos. La siguiente aplicación le parece derecho, aparte de conseguir el tipo de corrector para permitir funciones que aceptan subtipos de 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"); 
    } 

Lo más cerca que he recibido es añadir las funciones de un objeto de forma individual, y luego llamar a visitar en una valor:

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

esto puede ser usado de esta manera:

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

alguna idea de cómo hacer esto con una sola llamada al método?

+2

poco se parece a un diccionario donde la clave es un tipo y el valor es una función ... – Roly

+1

¿Está utilizando C# 3 o 4?En C# 4, el tipo de Func es contravariante en sus tipos de parámetros formales, lo que le brinda más flexibilidad en las conversiones. –

+0

@Eric Lippert: en este caso, * creo que * realmente quiero la covarianza en lugar de la contravariancia. Quiero aceptar funciones que podrían no ser capaces de aceptar parámetros de tipo T (mientras que normalmente querría aceptar cualquier función que acepte parámetros de tipo T, que incluye funciones que aceptan parámetros de tipo U, donde T <: U) –

Respuesta

9

Usando funcional C# (de @Alireza)

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

El hecho de que C# funcional parece usar el El mismo enfoque que utilicé, es decir, pasar cada lambda en llamadas a métodos separados, parece sugerir que hacerlo todo en una sola llamada a un método simplemente no es posible (al menos, mientras se mantiene la seguridad del tipo). Ah bueno. –

14

Coincidencia de patrones es una de esas características preciosas en su mayoría se encuentran en los lenguajes de programación funcionales como F #. Hay un gran proyecto en codeplex llamado Functional C#. Considere el siguiente F # código:

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) 

Utilización de la biblioteca funcional C#, C# equivalente sería:

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

Para para garantizar la coincidencia total de patrones, necesitaría construir la función en el tipo mismo. Así es como yo lo haría:

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

Esto es esencialmente una implementación del patrón de visitante, excepto que en lugar de una clase con dos métodos pasa dos lambdas. –

1

Compruebe hacia fuera mi patrón de aplicación a juego: repo

Se basa en expresiones, por lo que ofrece igualdad del perfomance con ifs anidados.

uso

Ejemplo:

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 

gratuito disponible en todo NuGet: Nuget package

+0

Publique los fragmentos de código necesarios, por favor. –

0

aplicación genérica que estoy usando, que puede coincidir con el tipo, condición o un valor:

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

Y en su caso, el uso se vería así:

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

Algunos otros ejemplos:

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