воскресенье, 24 января 2010 г.

Dynamic Expressions

Всё началось с того, что был нужен простенький DSL, который бы возвращал бы bool по некоторому словарю объектов, т.е. удовлетворяет ли он условию заданному через DSL. Было у меня два поста, о том как написать свой парсер языка и о том, как с помощью Expressions tree, обработать дерево языка построенное парсером. Так я и делал вначале. Получилось несколько громоздко, меня покритиковали :) Поэтому решил упростить себе и другим жизнь, вот на этой странице C# Samples прочитав внимательно соглашение и нажав I accept, можно скачать набор примеров, в частности большое количество примеров по LINQ. И среди них есть папка LinqSamples\DynamicQuery. Там есть файл Dynamic.cs с набором классов, которые делают то, что я описал ранее, т.е. преобразуют текстовую запись LINQ в Expressions tree и далее компиляция его в делегат. Это просто замечательно, то что доктор прописал, готово и отлично работает. Внутри папки есть Dynamic Expressions.html файл с подробной инструкцией, а также таблицей поддерживаемых методов.
Вот например, то что я писал выше, создание простого калькулятора из примера:
  1. ParameterExpression argument = Expression.Parameter(typeof(int), "argument");  
  2. LambdaExpression e = DynamicExpression.ParseLambda(new ParameterExpression[] { argument }, null"argument + argument");  

Я использовал в своём приложении, очень удобно. Единство, пришлось сделать небольшой хак.


У нас есть Document, это просто класс, который наследуется от стандартного Dictionary. К нему надо применить это выражение на DSL, т.е. в результате будет Func<Document, bool>, но в DSL включены дополнительные функции, которые специфичны для нашего приложения, т.е. к примеру regexp, надо применить какой-то regexp к конкретному полю и выдать соответствует ли это поле документа ему.
Я создал адаптер для документа, который имеет набор методов, которые реализуют необходимые нам функции, например вот так:
  1. public object field(string fieldName)  
  2. {  
  3.     if (document.ContainsKey(fieldName))  
  4.         return document[fieldName];  
  5.   
  6.     return null;  
  7. }  
  8.   
  9. public bool regexp(string fieldName, string regexp)  
  10. {  
  11.     if (regexp == null)  
  12.         return false;  
  13.   
  14.     var pattern = new Regex(regexp);  
  15.     var field = field(fieldName);  
  16.     if (field == null)  
  17.         return false;  
  18.   
  19.     return pattern.IsMatch(field.ToString());  
  20. }  
По строковому выражению на DSL я строю необходимый мне делегат:
  1. var documentExpression = DynamicExpression.ParseLambda<Document, bool>(expression);  
  2. Func<Document, bool> func = documentExpression .Compile();  
Пример expression:
  1. regexp("Field1""0x.*") and "Test".Equals(field("Field2"));  
Ну и при вызове функции придется заворачивать документ в адаптер.
И ещё по непонятным причинам нельзя вызывать у передаваемого объекта методы, чтобы решить эту проблему пришлось в файле Dynamic.cs за комментировать строчку:
  1. static bool IsPredefinedType(Type type) {  
  2.     //foreach (Type t in predefinedTypes) if (t == type) return true;  
  3.         return true;  
  4. }  
Грязный хак, но работает.
Вот так просто можно пропустить предыдущие два сообщения посвященных компиляции в .NET и просто использовать готовый класс.
Ну и напоследок ссылка на большой пример использования Dynamic LINQ To Entities Queries Using WCF/WPF demo code.

Комментариев нет:

Отправить комментарий