如何使用 ExpressionTree 创建一个使用 Regex 的谓词

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

我正在手动构建一个谓词来过滤 CollectionView 中的数据,我想添加通过用户提供的 Regex 过滤特定字段的功能。直接写谓词会给出类似的东西:

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

所以我可以将模式传递到我的谓词工厂并执行类似这样的操作(在我的脑海中,没有尝试过 - 不确定 Call 语法):

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

但我的直觉告诉我,这将创建一个表达式,该表达式将在每次调用谓词时根据模式重建正则表达式。这种直觉正确吗?

如果我的直觉是正确的,那么是否可以在创建使用它的表达式之前预先构建正则表达式?如果是,怎么做?

或者,如果我完全偏离正轨,这应该由谁来完成?

(我的 Call 语法是否正确??)


编辑

只是为了澄清一些事情。

  1. 我正在构建的谓词是针对 CollectionView.Filter 的,因此签名必须是 Predicate<object>
  2. 尽管我在示例中只显示了一个正则表达式,但我实际(动态)构建的谓词还有许多其他子句。为清楚起见,其余部分已被省略。
  3. 谓词本身仅在用户单击某些选项然后按下按钮后才构建。即使与其他 UI 活动相比,这种情况也很少发生。
  4. 应用谓词时,它将应用于支持 CollectionView 的集合中的 10,000 到 20,000 个(或更多)对象
  5. 我的程序中很少有其他正则表达式,所以我认为 Filip 关于缓存最后 15 个模式的观察意味着我的直觉可能是错误的。
  6. 但我仍然想做一些像 Filip 的回答那样的事情,并以某种方式在我正在构建的表达式树中捕获正则表达式的编译版本。

这只会编译表达式的 属性 部分一次。希望对您有所帮助。

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

只要您有对谓词的引用,它就会使用附带的正则表达式。但最好的检查方法是 运行 它在一些测试数据上,看看你会得到什么。 Regex.IsMatch(String, String) 方法默认缓存最后 15 个最近使用的静态正则表达式模式。所以如果你没有超过限制,表达式的实现不应该重新编译整个东西。但最好的方法是尽可能多地进行测试,看看会发生什么。

首先,根本不清楚为什么要使用表达式树/动态代码生成。性能真的那么重要,以至于您负担不起创建复合 Predicate 加入其他(较小的)Predicate 吗?

其次,我不确定我是否理解您在更改代码以使用已编译 RegEx 时看到的问题。以下代码是否符合您的要求:

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

请注意 RegexOptions.Compiled actually does 可能与您的预期不完全相同,但在您的上下文中似乎有意义。