Этот фрагмент компилирует Правила в быстрый исполняемый код (используя деревья выражений ) и не требует каких-либо сложных операторов switch:
(Изменить: полный рабочий пример с универсальным методом )
public Func<User, bool> CompileRule(Rule r)
{
var paramUser = Expression.Parameter(typeof(User));
Expression expr = BuildExpr(r, paramUser);
// build a lambda function User->bool and compile it
return Expression.Lambda<Func<User, bool>>(expr, paramUser).Compile();
}
Вы можете написать:
List<Rule> rules = new List<Rule> {
new Rule ("Age", "GreaterThan", "20"),
new Rule ( "Name", "Equal", "John"),
new Rule ( "Tags", "Contains", "C#" )
};
// compile the rules once
var compiledRules = rules.Select(r => CompileRule(r)).ToList();
public bool MatchesAllRules(User user)
{
return compiledRules.All(rule => rule(user));
}
Вот реализация BuildExpr:
Expression BuildExpr(Rule r, ParameterExpression param)
{
var left = MemberExpression.Property(param, r.MemberName);
var tProp = typeof(User).GetProperty(r.MemberName).PropertyType;
ExpressionType tBinary;
// is the operator a known .NET operator?
if (ExpressionType.TryParse(r.Operator, out tBinary)) {
var right = Expression.Constant(Convert.ChangeType(r.TargetValue, tProp));
// use a binary operation, e.g. 'Equal' -> 'u.Age == 15'
return Expression.MakeBinary(tBinary, left, right);
} else {
var method = tProp.GetMethod(r.Operator);
var tParam = method.GetParameters()[0].ParameterType;
var right = Expression.Constant(Convert.ChangeType(r.TargetValue, tParam));
// use a method call, e.g. 'Contains' -> 'u.Tags.Contains(some_tag)'
return Expression.Call(left, method, right);
}
}
Обратите внимание, что я использовал «GreaterThan» вместо «great_than» и т. Д. - это потому, что «GreaterThan» - это имя .NET для оператора, поэтому нам не нужно никакого дополнительного сопоставления.
Если вам нужны собственные имена, вы можете создать очень простой словарь и просто перевести все операторы перед составлением правил:
var nameMap = new Dictionary<string, string> {
{ "greater_than", "GreaterThan" },
{ "hasAtLeastOne", "Contains" }
};
Код использует тип User для простоты. Вы можете заменить пользователя универсальным типом T, чтобы иметь универсальный компилятор правил для любых типов объектов. Кроме того, код должен обрабатывать ошибки, такие как неизвестное имя оператора.
Обратите внимание, что генерация кода на лету была возможна еще до того, как был представлен API деревьев выражений, с использованием Reflection.Emit. Метод LambdaExpression.Compile () использует Reflection.Emit под обложками (вы можете увидеть это с помощью ILSpy ).
Вот код, который компилируется как есть и выполняет свою работу. В основном используются два словаря, один из которых содержит сопоставление имен операторов с логическими функциями, а другой содержит сопоставление имен свойств типа User с PropertyInfos, используемым для вызова метода получения свойств (если он общедоступен). Вы передаете экземпляр User и три значения из своей таблицы статическому методу Apply.
источник
Я создал механизм правил, который использует иной подход, чем тот, который вы описали в своем вопросе, но я думаю, вы найдете его гораздо более гибким, чем ваш нынешний подход.
Ваш текущий подход, кажется, сфокусирован на одной сущности «Пользователь», а ваши постоянные правила определяют «имя свойства», «оператор» и «значение». Вместо этого мой шаблон хранит код C # для предиката (Func <T, bool>) в столбце «Выражение» в моей базе данных. В текущем проекте, используя генерацию кода, я запрашиваю «правила» из моей базы данных и компилирую сборку с типами «Правило», каждый с методом «Test». Вот подпись для интерфейса, который реализуется каждым правилом:
«Выражение» компилируется как тело метода «Test» при первом запуске приложения. Как вы можете видеть, другие столбцы в таблице также отображаются как первоклассные свойства правила, так что разработчик может гибко настраивать способ получения пользователем уведомления о сбое или успехе.
Генерация сборки в памяти происходит один раз во время вашего приложения, и вы получаете выигрыш в производительности, не используя рефлексию при оценке ваших правил. Ваши выражения проверяются во время выполнения, поскольку сборка не будет генерироваться правильно, если имя свойства введено с ошибкой и т. Д.
Механика создания сборки в памяти заключается в следующем:
На самом деле это довольно просто, потому что для большинства этот код является реализацией свойств и инициализацией значения в конструкторе. Кроме того, единственным другим кодом является выражение.
ПРИМЕЧАНИЕ: есть ограничение, что ваше выражение должно быть .NET 2.0 (без лямбда-выражений или других функций C # 3.0) из-за ограничения в CodeDOM.
Вот пример кода для этого.
Помимо этого я создал класс под названием «DataRuleCollection», который реализовал ICollection>. Это позволило мне создать возможность «TestAll» и индексатор для выполнения определенного правила по имени. Вот реализации для этих двух методов.
БОЛЬШЕ КОДА: Был запрос на код, связанный с генерацией кода. Я инкапсулировал функциональность в классе под названием «RulesAssemblyGenerator», который я включил ниже.
Если есть какие-либо другие вопросы или комментарии или запросы на дополнительные примеры кода, дайте мне знать.
источник
Отражение - ваш самый универсальный ответ. У вас есть три столбца данных, и они должны обрабатываться по-разному:
Ваше имя поля. Отражение - это способ получить значение из кодированного имени поля.
Ваш оператор сравнения. Их должно быть ограниченное количество, поэтому оператор case должен обрабатывать их наиболее легко. Тем более, что некоторые из них (имеет один или несколько из них) немного сложнее.
Ваше значение сравнения. Если это все прямые значения, то это легко, хотя вам придется разделить несколько записей. Однако вы также можете использовать отражение, если они также являются именами полей.
Я бы выбрал такой подход, как:
и т. д.
Это дает вам гибкость для добавления дополнительных параметров для сравнения. Это также означает, что вы можете кодировать в методах сравнения любой тип проверки, который вам может потребоваться, и делать их настолько сложными, насколько вам нужно. Здесь также есть опция для сравнения CompareTo как рекурсивного обратного вызова к другой строке или значения поля, что можно сделать следующим образом:
Все зависит от возможностей на будущее ....
источник
Если у вас есть только несколько свойств и операторов, путь наименьшего сопротивления состоит в том, чтобы просто закодировать все проверки как особые случаи, подобные этому:
Если у вас много свойств, вы можете найти подход, основанный на использовании таблиц, более приемлемым. В этом случае вы должны создать статический
Dictionary
объект, который отображает имена свойств на делегатов, соответствующих, скажем,Func<User, object>
.Если вы не знаете имен свойств во время компиляции или хотите избежать особых случаев для каждого свойства и не хотите использовать табличный подход, вы можете использовать отражение для получения свойств. Например:
Но с тех пор
TargetValue
это, вероятноstring
, вам нужно позаботиться о преобразовании типов из таблицы правил, если это необходимо.источник
IComparable
используется для сравнения вещей. Вот документы: Метод IComparable.CompareTo .Как насчет подхода, ориентированного на тип данных, с методом расширения:
Чем вы можете оценить, как это:
источник
Хотя наиболее очевидный способ ответить на вопрос «Как реализовать механизм правил? (В C #)») - это выполнить заданный набор правил в последовательности, это обычно рассматривается как наивная реализация (не означает, что она не работает :-)
Кажется, это «достаточно хорошо» в вашем случае, потому что ваша проблема, скорее, заключается в том, «как запустить набор правил в последовательности», и лямбда / дерево выражений (ответ Мартина), безусловно, самый элегантный способ в этом отношении, если вы оснащены последними версиями C #.
Однако для более сложных сценариев здесь приведена ссылка на алгоритм Rete, который фактически реализован во многих коммерческих системах обработчиков правил, и другая ссылка на NRuler , реализацию этого алгоритма в C #.
источник
Ответ Мартина был довольно хорошим. Я на самом деле создал движок правил, который имеет ту же идею, что и его. И я был удивлен, что это почти то же самое. Я включил часть его кода, чтобы несколько улучшить его. Хотя я сделал это для обработки более сложных правил.
Вы можете посмотреть на Yare.NET
Или загрузите его в Nuget
источник
Как насчет использования механизма правил рабочего процесса?
Вы можете выполнять правила рабочего процесса Windows без рабочего процесса, см. Блог Гая Бурштейна: http://blogs.microsoft.co.il/blogs/bursteg/archive/2006/10/11/RuleExecutionWithoutWorkflow.aspx
и чтобы программно создать свои правила, см. WebLog Стивена Кауфмана
http://blogs.msdn.com/b/skaufman/archive/2006/05/15/programmatically-create-windows-workflow-rules.aspx
источник
Я добавил реализацию для и, или между правилами, я добавил класс RuleExpression, представляющий корень дерева, который может быть простым правилом или может быть и, или двоичные выражения, поскольку они не имеют правила и имеют выражения:
У меня есть другой класс, который компилирует ruleExpression в один
Func<T, bool>:
источник