How to use an ExpressionTree to create a predicate that utilizes a Regex

一笑奈何 提交于 2020-01-05 07:04:09

问题


I am manually building a predicate for filtering data in a CollectionView and I want to add the ability to filter a particular field via a user supplied Regex. Writing the predicate directly would give something like:

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

So I could pass the pattern in to my Predicate Factory and do something like this (off the top of my head and not tried - not sure about the 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());

But my gut feeling says that this will create an expression that will rebuild the Regex from the pattern on every call to the predicate. Is this gut feeling correct?

If my gut feeling is correct then is it possible to pre-build the Regex before creating the Expression that consumes it? And if so, how?

Or if I am totally off the beam, who should this be done?

(Also is my Call syntax correct??)


Edit

Just to clear up some things.

  1. The predicate I am building is destined for a CollectionView.Filter, so the signature has to be Predicate<object>
  2. Even though I am only showing a Regex in my example, the predicate I am actually building (dynamically) has many other clauses. The rest have been left out for clarity.
  3. The predicate itself is only built after the user clicks around some options and then presses a button. This is done infrequently even when compared to other UI activities.
  4. When the predicate is applied, it will be applied to 10,000 to 20,000 (or more) objects in the collection backing the CollectionView
  5. There are very few other Regex's on my program, so I think Filip's observation about caching the last 15 patterns means that my gut feeling is probably wrong.
  6. But I still want to do something like Filip's answer and somehow capture a compiled version of the Regex in the Expression Tree that I am building.

回答1:


First of all, it is not clear why do you use Expression Tree / dynamic code generation at all. Is performance really that critical that you can't afford creating your compound Predicate joining other (smaller) Predicates?

Secondly, I'm not sure I understand where you see issues with changing your code to use compiled RegEx. Is following code what you wanted:

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

Note that what RegexOptions.Compiled actually does might be not exactly what you expected but it seems to make sense in your context.




回答2:


This will only compile once the property portion of the expression. Hope this will help you a bit.

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

As long as you have the reference to the Predicate it will use the came regex. But the best way to check this is just to run it on some test data and see what you will get. The method Regex.IsMatch(String, String) by default caches the last 15 most recently used static regular expression patterns. So if you don't go over the limit the expression implementation should not recompile the whole thing. But the best way is to just test as much as you can and see what happens.



来源:https://stackoverflow.com/questions/44089953/how-to-use-an-expressiontree-to-create-a-predicate-that-utilizes-a-regex

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!