2017-05-20 2 views
1

Ich erstelle manuell ein Prädikat für das Filtern von Daten in einer CollectionView und ich möchte die Möglichkeit hinzufügen, ein bestimmtes Feld über einen Benutzer bereitgestellten Regex zu filtern. Schreibt das Prädikat würde direkt geben so etwas wie:Wie verwende ich einen ExpressionTree, um ein Prädikat zu erstellen, das einen Regex verwendet?

string userRegex = "abc.+"; 
Predicate<object> myPredicate = p => Regex.IsMatch(((MyType).p).MyField, userRegex); 

So konnte ich das Muster in meine Prädikat Fabrik passieren und tue so etwas wie dies (aus der Spitze von meinem Kopf und nicht versuchte - nicht sicher über die Call-Syntax) :

string userRegex = "abc.+"; 
var paramObject = Expression.Parameter(typeof(object), "p"); 
var paramMyType = Expression.TypeAs(paramObject, typeof(MyType)); 
var propMyField = Expression.Property(paramMyType, "MyField"); 
var constRegex = Expression.Constant(userRegex); 

var methodInfo = typeof(Regex).GetMethod("IsMatch", new Type[] { typeof(string), typeof(string) }); 
var params = new Expression[] { propMyField, constRegex } 

var lamdaBody = Expression.Call(methodInfo, params); 
var lamda = Expression.Lambda<Func<object, bool>>(lamdaBody, paramObject); 
var myPredicate = new Predicate<object>(lamda.Compile()); 

Aber mein Bauchgefühl sagt, dass dies einen Ausdruck zu schaffen, die die Regex aus dem Muster bei jedem Aufruf zum Prädikat wieder aufbauen werden. Ist das Bauchgefühl richtig?

Wenn mein Bauchgefühl korrekt ist, ist es dann möglich, die Regex vor dem Erstellen des Ausdrucks, der es verbraucht, zu erstellen? Und wenn ja, wie?

Oder wenn ich total aus dem Strahl, wer sollte dies getan werden?

(Auch ist mein Aufruf Syntax korrekt ??)


bearbeiten

einfach ein paar Dinge zu klären.

  1. Das Prädikat I ist für ein CollectionView.Filter bestimmt sind Gebäude, so dass die Unterschrift Predicate<object>
  2. Auch sein muss, obwohl ich nur eine Regex in meinem Beispiel zeige, bin das Prädikat mir eigentlich (dynamisch) Gebäude hat viele andere Klauseln. Der Rest wurde aus Gründen der Klarheit weggelassen.
  3. Das Prädikat selbst wird erst erstellt, nachdem der Benutzer einige Optionen angeklickt und dann eine Taste gedrückt hat. Dies geschieht selten im Vergleich zu anderen UI-Aktivitäten.
  4. Wenn das Prädikat angewendet wird, wird es auf 20.000 (oder mehr) bis 10.000 angewandt werden, um die Sammlung Objekte in der Collectionplay
  5. Es gibt nur sehr wenige andere Regex auf meinem Programm, so denke ich, Filip Beobachtung über die letzte Cachen 15 Muster bedeuten, dass mein Bauchgefühl wahrscheinlich falsch ist.
  6. Aber ich möchte immer noch etwas wie Filips Antwort machen und irgendwie eine kompilierte Version des Regex in den Ausdrucksbaum einfangen, den ich gerade erstelle.
+0

'Benutzer geliefert Regex' - kein kugelsicherer Weg überhaupt. Was werden Sie in die Ausnahmebehandlung einfügen? – sln

+0

@sln Ja Ich habe verschiedene Dinge aus Gründen der Klarheit weggelassen. Wie auch immer, in meiner tatsächlichen Anwendung hat die UI-Ebene (eigentlich das View-Modell, das an das Steuerelement gebunden ist, das alle Benutzerauswahlen für die Filterparameter behandelt) die Zeichenfolge bereits als Regex validiert, bevor die Daten irgendwo in die Nähe meiner Prädikat-Factory gelangen. –

+0

'validiert die Zeichenfolge als eine Regex' so weit ich weiß, die einzige Möglichkeit, eine Dot-Net Regex zu validieren, ist es zu kompilieren, es sei denn, Sie wissen, wie Sie es vorher analysieren. Net tut. – sln

Antwort

4

Zunächst einmal ist nicht klar, warum Sie überhaupt Expression Tree/Dynamic Code Generation verwenden.Ist die Leistung wirklich so wichtig, dass Sie es sich nicht leisten können, Ihre Verbindung Predicate mit anderen (kleineren) Predicate s zu erstellen?

Zweitens bin ich mir nicht sicher, ich verstehe, wo Sie Probleme mit der Änderung Ihres Codes zur Verwendung kompiliert RegEx sehen. Ist folgenden Code, was Sie wollten:

static Predicate<object> CreateRegExPredicateSmart(string pattern) 
    { 
     var regex = new Regex(pattern, RegexOptions.Compiled); 
     var paramObject = Expression.Parameter(typeof(object), "p"); 
     var paramMyType = Expression.TypeAs(paramObject, typeof(MyType)); 
     var propMyField = Expression.Property(paramMyType, "MyField"); 
     var constRegex = Expression.Constant(regex); 

     var methodInfo = typeof(Regex).GetMethod("IsMatch", new Type[] { typeof(string) }); 
     var paramsEx = new Expression[] { propMyField }; 

     var lamdaBody = Expression.Call(constRegex, methodInfo, paramsEx); 
     Expression<Func<object, bool>> lamdaSmart = Expression.Lambda<Func<object, bool>>(lamdaBody, paramObject); 

     return new Predicate<object>(lamdaSmart.Compile()); 
    } 

Hinweis dass das, was RegexOptions.Compiled actually does vielleicht nicht genau das, was Sie erwartet, aber es scheint Sinn in Ihrem Kontext zu machen.

+0

1) Ich baue mein Prädikat aus dynamischem Code, da ich ungefähr 20 völlig verschiedene, unabhängige, potentielle Klauseln habe (die zur Laufzeit vom Benutzer ausgewählt werden, je nachdem, was sie wollen), die ich gegen mehr als 20.000 Objekte testen muss. 2) Ich war mir nicht sicher, wie ich die kompilierte Regex im Prädikat erfassen sollte, da ich in diesem bestimmten Bereich von C# neu bin. Ihre Antwort ist das erste Beispiel, das ich gesehen habe und das für mich einen Sinn ergibt. Ich dachte immer, 'Expression.Constant' sei ein fester Wert und kein Objekt, gegen das man einen Funktionsaufruf ausgeben könnte. –

+0

Ich habe gerade Ihren Code implementiert und es hat perfekt funktioniert. Vielen Dank! –

0

Dies kompiliert nur einmal den Eigenschaftsteil des Ausdrucks. Hoffe, das wird dir ein bisschen helfen.

public static class PropertyGetter<T> 
    { 
     private static Dictionary<string, Func<T, string>> cache = new Dictionary<string, Func<T, string>>(); 

     public static Func<T, string> Get(string propertyName) 
     { 
      if (!cache.ContainsKey(propertyName)) 
      { 
       var param = Expression.Parameter(typeof(T)); 
       Expression<Func<T, string>> exp = Expression.Lambda<Func<T, string>>(Expression.Property(param, propertyName),param); 
       cache[propertyName] = exp.Compile(); 
      } 
      return cache[propertyName]; 
     } 

     public static Predicate<object> GetPredicate(string propertyName, string pattern) 
     { 
      Func<T, string> getter = Get(propertyName); 
      Regex regex = new Regex(pattern, RegexOptions.Compiled); 

      return (obj) => regex.IsMatch(getter((T)obj));   } 
    } 

Solange Sie den Verweis auf das Prädikat haben, wird es die Came Regex verwenden. Aber der beste Weg, dies zu überprüfen, ist nur, um es auf einige Testdaten auszuführen und zu sehen, was Sie bekommen werden. Die Methode Regex.IsMatch(String, String) speichert standardmäßig die letzten 15 zuletzt verwendeten statischen regulären Ausdrucksmuster. Wenn Sie also nicht über das Limit gehen, sollte die Implementierung des Ausdrucks das Ganze nicht neu kompilieren. Aber der beste Weg ist, so viel wie möglich zu testen und zu sehen, was passiert.

+0

Ich werde das morgen oder Montag anschauen. Ich habe Ihren Code noch nicht vollständig durchgearbeitet (und meine Prädikat-Factory fügt auch dynamisch weitere 18-20 Klauseln hinzu - aber das Prädikat wird nur selten neu aufgebaut), aber ich denke, das Wichtigste ist das Caching der letzten 15 Regex-Muster. Ich denke, das sagt mir, dass mein Bauchgefühl falsch war. Zusammenfassend ist das Muster ziemlich stabil, so dass es wahrscheinlich zwischengespeichert wird, aber das Prädikat wird selten erstellt, so dass ich nicht sicher bin, ob das Caching benötigt wird. Das Prädikat wird jedoch auf 10.000 bis 20.000 Elemente angewendet. –

Verwandte Themen