Первое, на что нужно обратить внимание при использовании NRules DSL, это то, что происходит, когда вы объявляете переменную соответствия в правиле и связываетесь с ним:
Apple apple = null;
When()
.Match(() => apple);
Этой переменной фактически никогда не назначается значение. Он захватывается как дерево выражений, и его имя извлекается и используется для последующего поиска других выражений, ссылающихся на эту же переменную. Затем двигатель заменяет эти ссылки фактическим сопоставленным фактом.
Например:
Then()
.Do(ctx => ctx.Insert(new Basket(apple)));
Здесь «яблоко» - это та же переменная яблока, что и в предложении When, поэтому NRules распознает это и корректно объединяет выражения.
Когда вы извлекли метод расширения, вы назвали переменную "fruit":
public static IRightHandSideExpression AddToBasket(this IRightHandSideExpression rhs, IFruit fruit)
{
return rhs.Do(ctx => ctx.Insert(new Basket(fruit)));
}
Движок больше не распознает это как одну и ту же ссылку на факт, поскольку «фрукты» и «яблоко» не совпадают.
Итак, исправление # 1 состоит в том, чтобы просто назвать переменную так же, как объявление:
public static class DslExtensions
{
public static IRightHandSideExpression AddToBasket(this IRightHandSideExpression rhs, IFruit apple)
{
return rhs.Do(ctx => ctx.Insert(new Basket(apple)));
}
}
Очевидно, что это не идеально, так как вы полагаетесь на совпадение имен переменных.
Поскольку NRules работает в терминах деревьев выражений, лучшим способом построения универсального метода расширения было бы также записать его в терминах в деревьях выражений и больше не зависеть от именования переменных.
Итак, исправление №2 - написать метод расширения с использованием лямбда-выражений.
public class TestRuleWithExtension : RuleBase
{
public override void Define()
{
base.Define();
Apple apple = null;
When()
.Match(() => apple);
Then()
.AddToBasket(() => apple);
}
}
public static class DslExtensions
{
public static IRightHandSideExpression AddToBasket(this IRightHandSideExpression rhs, Expression<Func<IFruit>> alias)
{
var context = Expression.Parameter(typeof(IContext), "ctx");
var ctor = typeof(Basket).GetConstructor(new[] {typeof(IFruit)});
var newBasket = Expression.New(ctor, alias.Body);
var action = Expression.Lambda<Action<IContext>>(
Expression.Call(context, nameof(IContext.Insert), null, newBasket),
context);
return rhs.Do(action);
}
}
Обратите внимание, что AddToBasket(() => apple)
теперь захватывает лямбда-выражение, которое позже извлекается и используется в реализации метода расширения. С некоторой магией выражения я затем создал лямбда-выражение, эквивалентное тому, которое вы имели, но на этот раз не полагаясь на какое-либо конкретное именование переменных.