Ein weiteres Pattern Matching in C #

Während der Arbeit mit der C # -Sprache wurde es mir immer wichtiger, einen Mechanismus für die Mustererkennung zu finden, der in vielen modernen Multi-Paradigmen-Sprachen (F #, Scala usw.) vorhanden ist, in C # jedoch nicht verfügbar ist. Implementierungen, die dank einer halben Stunde googeln ( Beispiel ) gefunden wurden, schlugen vor, Übereinstimmungsausdrücke unter Verwendung fließender Schnittstellen zu konstruieren, was meiner Meinung nach eine recht umständliche Syntax ist. Der zweite Nachteil, der bereits für den tatsächlichen Gebrauch von größerer Bedeutung ist, ist die Suche nach Prädikaten in der Schleife, die in solchen Messgeräten "unter der Haube" auftritt. Im Allgemeinen habe ich mich vorgenommen, eine eigene Implementierung des Vergleichs mit der Stichprobe zu schreiben, die sich an zwei Grundprinzipien orientiert:
  • Syntaktisch näher an "normalen" Konstrukten als in F # / Scala
  • Gehen Sie so nah wie möglich heran, um mit if / else zu codieren, wenn / else so weit wie möglich


Für was ist passiert - ich bitte um einen Schnitt


Aussehen


Anfangs wollte ich das Design des Matchers wie ein "echtes" aussehen lassen, um Kettenaufrufe auf Methoden zu vermeiden. Es wurde beschlossen, eine Liste von Paaren "Prädikat - Funktion" zu verwenden. Um die Syntax der Kurzlisteninitialisierung zu verwenden, implementiert die Matcher-Klasse IEnumerable und verfügt außerdem über eine Add-Methode. Zum leichteren Verwendung (zum Beispiel für die Übertragung zum Select), in der Klasse wurde hinzugefügt Verfahren ist implizit in die Func <>

Dies ist , wie es aussieht , wenn Sie verwenden:
Func match = new Matcher
{
    {s => string.IsNullOrEmpty(s), s => 0},
    {s => true, s => s.Length}
};
int len1 = match(null); // 0
int len2 = match("abc"); // 3


Implementierung


Die erste Implementierung, die im Rahmen der Syntaxsuche geschrieben wurde, war „naiv“ und führte wie die gefundenen die sequentielle Ausführung von Prädikaten mit dem an Match übergebenen Parameter durch. Als der Code beim ersten Punkt zufriedenstellend wurde (äußerlich nicht sperrig), habe ich den Matcher mit Expression <> neu geschrieben:

public class ExprMatcher : IEnumerable>, Expression>>>
    {
        private Func _matcher;
        private Func Matcher
        {
            get { return _matcher ?? (_matcher = CompileMatcher()); }
        } 
        private readonly List>, Expression>>> _caseList = new List>, Expression>>>();
        public void Add(Expression> predicate, Expression> function)
        {
            _caseList.Add(new Pair>, Expression>>(predicate, function));
        }
        private Func CompileMatcher()
        {
            var reverted = Enumerable.Reverse(_caseList).ToList();
            var arg = Expression.Parameter(typeof(TIn));
            var retVal = Expression.Label(typeof(TOut));
            var matcher = Expression.Block(
                        Expression.Throw(Expression.Constant(new MatchException("Provided value was not matched with any case"))),
                        Expression.Label(retVal, Expression.Constant(default(TOut)))
                    );
            foreach (var pair in reverted)
            {
                retVal = Expression.Label(typeof(TOut));
                var condition = Expression.Invoke(pair.First, arg);
                var action = Expression.Return(retVal, Expression.Invoke(pair.Second, arg));
                matcher = Expression.Block(
                    Expression.IfThenElse(condition, action, Expression.Return(retVal, matcher)),
                    Expression.Label(retVal, Expression.Constant(default(TOut)))
                    );
            }
            return Expression.Lambda>(matcher, arg).Compile();
        }
        public TOut Match(TIn value)
        {
            return Matcher(value);
        }
        public static implicit operator Func(ExprMatcher matcher)
        {
            return matcher.Match;
        }
        public IEnumerator>, Expression>>> GetEnumerator()
        {
            return _caseList.GetEnumerator();
        }
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }


Beim Aufrufen der Match () -Methode oder beim Umwandeln in Func wird ein verketteter Ausdruck erstellt, der eine MatchException auslöst, wenn das Argument keinem der Prädikate entspricht. Als Ergebnis erhalten wir nur einen Overhead in Form der Kompilierungszeit für Expression.

Algebraische Typen


Ein weiterer Nachteil der Verwendung von C # für mich war das Fehlen von Union-Typen. Ich wollte sie hinzufügen, aber gleichzeitig ihre Verwendung so sicher (für NPE) wie möglich machen.
Für den Anfang wurde eine Kombination von zwei Typen implementiert:
public sealed class Union
{
	public object Value { get; private set; }
	public T1 Value1 { get; private set; }
	public T2 Value2 { get; private set; }
	public Union(T1 value)
        {
                Value1 = value;
	        Value = value;
        }
	public Union(T2 value)
        {
                Value2 = value;
		Value = value;
        }
	public static explicit operator T1(Union value)
        {
                return value.Value1;
        }
	public static explicit operator T2(Union value)
        {
                return value.Value2;
        }
        public static implicit operator Union(T1 value)
        {
                return new Union(value);
        }
        public static implicit operator Union(T2 value)
        {
               return new Union(value);
        }
}

Abhängig von dem an den Konstruktor übergebenen Parameter wird in der Instanz entweder Value1 oder Value2 initialisiert, und Value wird ebenfalls initialisiert. Auf diese Weise können Sie die Überprüfung des Werttyps im Prädikat mit Hilfe von is vergleichen, ohne sich Sorgen machen zu müssen, dass der Wert einen anderen Typ als T1 und T2 annimmt. Mit der t4 Template Engine wurden Union-Überladungen von bis zu 17 Typen generiert.
Um die Initialisierung der Matcher zu vereinfachen, wurden auch die Erben von Matcher und ExprMatcher geschrieben:
public class ExprMatcher : ExprMatcher> {}


Um das Bild zu vervollständigen, wurde auch eine eher triviale Option geschrieben.

Ich hoffe, dass mein Matcher jemandem nützlich sein wird:
Projekt auf bitbucket
Nuget-Paket

Vielen Dank für Ihre Aufmerksamkeit!

Jetzt auch beliebt: