【问题标题】:How to use an ExpressionTree to create a predicate that utilizes a Regex如何使用 ExpressionTree 创建一个使用 Regex 的谓词
【发布时间】:2017-05-20 19:20:23
【问题描述】:

我正在手动构建一个谓词来过滤 CollectionView 中的数据,并且我想添加通过用户提供的正则表达式过滤特定字段的功能。直接编写谓词会给出如下结果:

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

所以我可以将模式传递给我的谓词工厂并做这样的事情(我的头脑中没有尝试过 - 不确定调用语法):

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

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

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

或者,如果我完全不在意,应该由谁来做?

(我的调用语法也正确吗??)


编辑

只是为了澄清一些事情。

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

【问题讨论】:

  • user supplied Regex - 根本不是防弹方式。你打算用异常处理做什么?
  • @sln 是的,为了清楚起见,我省略了各种项目。无论如何,在我的实际应用程序中,UI 层(实际上是绑定到处理过滤器参数的所有用户选择的控件的视图模型)已经在数据到达我的谓词工厂附近之前将字符串验证为正则表达式。
  • validated the string as a Regex 据我所知,验证 Dot-Net 正则表达式的唯一方法是编译它,除非您知道如何在 .Net 之前解析它。
  • 我通常只是在帮助器中使用正则表达式创建一个帮助器方法。在这种情况下,助手可以返回一个布尔值。
  • @jdweng 我可以这样做,除了我已经解耦了所有东西并且过滤器视图模型、谓词工厂和集合视图模型都在不同的对象中。所以我需要将谓词处理一段要传递的数据。这样做会破坏这种脱钩。

标签: c# regex expression predicate


【解决方案1】:

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

其次,我不确定我是否理解您在更改代码以使用已编译的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 的内容可能与您的预期不完全一致,但在您的上下文中似乎是有意义的。

【讨论】:

  • 1) 我正在从动态代码构建我的谓词,因为我有大约 20 个完全不同的、独立的潜在子句(在运行时由用户根据他们的需要选择),我需要针对 20,000 个进行测试+ 对象。 2) 我不确定如何在谓词中实际捕获已编译的正则表达式,因为我是 C# 的这个特定领域的新手。你的回答是我见过的第一个对我有意义的例子。我一直认为Expression.Constant 是一个固定值,而不是一个可以发出函数调用的对象。
  • 我刚刚实现了您的代码,它运行良好。谢谢!
【解决方案2】:

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

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 个最近使用的静态正则表达式模式。因此,如果您不超过限制,则表达式实现不应重新编译整个事物。但最好的方法是尽可能多地进行测试,看看会发生什么。

【讨论】:

  • 我打算明天或周一看看这个。我还没有完全完成你的代码(我的谓词工厂也动态地构建了另外 18-20 个子句 - 但谓词只是不经常重建),但我认为关键是缓存最后 15 个正则表达式模式。我想这告诉我我的直觉是错误的。因此,总而言之,该模式相当稳定,因此可能会被缓存,但谓词很少构建,因此我不确定您是否需要缓存。但是,谓词将应用于 10,000 到 20,000 个项目。
猜你喜欢
  • 2018-02-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多