18 Stimmen

Implementierung des Musterabgleichs in C#

In Scala können Sie Mustervergleiche verwenden, um ein Ergebnis in Abhängigkeit vom Typ der Eingabe zu erzeugen. Zum Beispiel:

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

In C# möchte ich idealerweise in der Lage sein, zu schreiben:

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

Ist das möglich? Wenn ich versucht habe, es als eine einzelne Methode zu schreiben, weiß ich nicht, wie ich die Generika angeben kann. Die folgende Implementierung scheint richtig zu sein, abgesehen davon, dass die Typüberprüfung Funktionen zulässt, die Subtypen von T akzeptieren:

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

Am nächsten komme ich, wenn ich die Funktionen einzeln zu einem Objekt hinzufüge und dann visit für einen Wert aufrufe:

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

Dies kann folgendermaßen verwendet werden:

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

Irgendeine Idee, wie man dies mit einem einzigen Methodenaufruf tun?

13voto

Alireza Maddah Punkte 5710

Der Musterabgleich ist eine dieser schönen Funktionen, die man meist in funktionalen Programmiersprachen wie F# findet. Es gibt ein großartiges Projekt auf Codeplex namens Funktionales C# . Betrachten Sie den folgenden F#-Code:

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)

Bei Verwendung der funktionalen C#-Bibliothek würde das C#-Äquivalent lauten:

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

9voto

Richard Schneider Punkte 34159

Functional C# verwenden (von @Alireza)

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

5voto

kvb Punkte 54045

Um eine vollständige Musterübereinstimmung zu gewährleisten, müssten Sie die Funktion in den Typ selbst einbauen. Hier ist, wie ich es tun würde:

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

1voto

danaslapman Punkte 11

Sehen Sie sich meine Implementierung des Mustervergleichs an: Repo

Sie basiert auf Ausdrücken und bietet daher die gleiche Leistung wie verschachtelte ifs.

Beispiel für die Verwendung:

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

Verfügbar über NuGet: Nuget-Paket

0voto

Hrvoje Hudo Punkte 8904

Ich verwende eine generische Implementierung, die mit dem Typ, der Bedingung oder einem Wert übereinstimmen kann:

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

Und in Ihrem Fall würde die Verwendung wie folgt aussehen:

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

Einige andere Beispiele:

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

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X