如何使用ExpressionTree创建一个使用正则表达式

时间:2017-05-20 19:20:24

标签: c# regex expression predicate

我手动构建一个谓词来过滤CollectionView中的数据,我想添加通过用户提供的Regex过滤特定字段的功能。直接编写谓词会产生类似的结果:

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

但我的直觉是,这将创建一个表达式,可以在每次调用谓词时从模式重建正则表达式。这种直觉是否正确?

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

或者,如果我完全偏离光束,应该由谁来完成?

(也是我的Call语法正确吗?)

修改

只是为了清理一些事情。

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

2 个答案:

答案 0 :(得分:4)

首先,目前尚不清楚为什么要使用表达式树/动态代码生成。性能是否真的非常重要,您无法承担创建复合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可能与您的预期完全不同,但在您的上下文中似乎有意义。

答案 1 :(得分:0)

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

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

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