Click here to Skip to main content
15,885,875 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more:
I am trying to build a rules engine using some elements of reflection and the System.Linq.Expressions namespace. I am trying to build a Lamdba expression in the form of
Expression.Lambda<Func<T, int>>(expressionToCompile, parameter).Compile();

Which is forcing me to return an integer out the compiled expression - I have tried removing the int from the function definition and this has caused me more issues.

What I have tried:

At the momment my code contains the following method:

private static void CompileRuleDefinition<T>(RuleDefinition<T> ruleDefinition)
        {
            var parameter = Expression.Parameter(typeof(T));
            Expression expresion = BuildCompoundConditions(ruleDefinition, parameter);
            Expression expressionToCompile = null;
            Expression thenClause = BuildAssignExpression<T>(parameter, ruleDefinition.ThenAction);

            if (expresion != null)
            {
                if (ruleDefinition.ThenAction != null && ruleDefinition.ElseAction == null)
                {
                    expressionToCompile =
                        Expression.Block(
                            Expression.IfThen(
                                expresion,
                                thenClause),
                            Expression.Constant(42));
                }

                if (ruleDefinition.ThenAction != null && ruleDefinition.ElseAction != null)
                {
                    Expression elseClause = BuildAssignExpression<T>(parameter, ruleDefinition.ElseAction);
                    expressionToCompile =
                        Expression.Block(
                            Expression.IfThenElse(
                                expresion,
                                thenClause,
                                elseClause),
                            Expression.Constant(42));
                }

                if (expressionToCompile != null)
                {
                    ruleDefinition.CompiledExpression =
                        Expression.Lambda<Func<T, int>>(expressionToCompile, parameter).Compile();
                }
            }
        }


I do not like the use of the command :
Expression.Constant(42));
this does nothing in my code but simply allows the integer to be returned. The types returned by the "Then Clause" and the "Else Clause" can varying from being "Void" (which was where my problem started as the action could be to call a void method on class <t>) through int, bool etc.

What I am looking for is a solution in which I don't have to have this piece of redundant code.

(yes the code does have an issue if the ThenAction is also NULL)
Posted
Updated 30-Dec-16 7:48am
Comments
Philippe Mori 30-Dec-16 12:24pm    
Instead of trying Func<T, void>, what would happen if you use Action<T> instead? An Action is essentially like a Func without return value.

I have recieved a suggestion to use Action instead of Func (for some reason this response does not appear). I have applied the changes and all my test are successfully - looks like a fix. Many thanks.


private static void CompileRuleDefinition<T>(RuleDefinition<T> ruleDefinition)
{
    var parameter = Expression.Parameter(typeof(T));
    Expression expresion = BuildCompoundConditions(ruleDefinition, parameter);
    Expression expressionToCompile = null;
    Expression thenClause = BuildAssignExpression<T>(parameter, ruleDefinition.ThenAction);

    if (expresion != null)
    {
        if (ruleDefinition.ThenAction != null && ruleDefinition.ElseAction == null)
        {
            expressionToCompile =
                Expression.Block(
                    Expression.IfThen(
                        expresion,
                        thenClause));
        }

        if (ruleDefinition.ThenAction != null && ruleDefinition.ElseAction != null)
        {
            Expression elseClause = BuildAssignExpression<T>(parameter, ruleDefinition.ElseAction);
            expressionToCompile =
                Expression.Block(
                    Expression.IfThenElse(
                        expresion,
                        thenClause,
                        elseClause));
        }

        if (expressionToCompile != null)
        {
            ruleDefinition.CompiledExpression =
                Expression.Lambda<Action<T>>(expressionToCompile, parameter).Compile();
        }
    }
}
 
Share this answer
 
I have further refined the solution by removing the outer Exception block:
private static void CompileRuleDefinition<T>(RuleDefinition<T> ruleDefinition)
{
    var parameter = Expression.Parameter(typeof(T));
    Expression expresion = BuildCompoundConditions(ruleDefinition, parameter);
    Expression expressionToCompile = null;
    Expression thenClause = BuildAssignExpression<T>(parameter, ruleDefinition.ThenAction);

    if (expresion != null)
    {
        if (ruleDefinition.ThenAction != null && ruleDefinition.ElseAction == null)
        {
            expressionToCompile = Expression.IfThen(expresion, thenClause);
        }

        if (ruleDefinition.ThenAction != null && ruleDefinition.ElseAction != null)
        {
            Expression elseClause = BuildAssignExpression<T>(parameter, ruleDefinition.ElseAction);
            expressionToCompile =
                Expression.IfThenElse(
                        expresion,
                        thenClause,
                        elseClause);
        }

        if (expressionToCompile != null)
        {
            ruleDefinition.CompiledExpression =
                Expression.Lambda<Action<T>>(expressionToCompile, parameter).Compile();
        }
    }
}
 
Share this answer
 
When you specify a Func, it has two parameters - T, and TResult:
C#
Func<T, TResult>

So your code is explicitly saying:
C#
Func<T, int>

Which declares that the function must return an integer value.
You can't "get rid" of the return type: Func(T, TResult) Delegate (System)[^] because every function must have one (even constructors have a return type, but it's implicit rather than explicit in the definition) though you may be able to replace int with a different type, or even with void - but I've not tried returning voids under these circumstances. It'll depend on the rest of the code you wrote and I have no idea what that looks like!
 
Share this answer
 
Comments
Scyldshefing 30-Dec-16 8:21am    
I cannot use void as a return type in the Lambda expression (certainly not the way I tried it).

if (expressionToCompile != null)
{
ruleDefinition.CompiledExpression =
Expression.Lambda<Func<T, void>>(expressionToCompile, parameter).Compile();
}

gets the following error message:

Keyword 'void' cannot be used in this context

If I replace the int with object and remove the Expression.Constant line I get the following exception in my unit test:

Test Name: TestIfThenElseCondition
Test FullName: UnitTestProject1.GivenIfThenElse.TestIfThenElseCondition
Test Source: c:\VSSolution\RulesEngine\UnitTestProject1\GivenIfThenElse.cs : line 17
Test Outcome: Failed
Test Duration: 0:00:00.0014237

Result Message:
Test method UnitTestProject1.GivenIfThenElse.TestIfThenElseCondition threw exception:
System.ArgumentException: Expression of type 'System.Void' cannot be used for return type 'System.Object'
Result StackTrace:
at System.Linq.Expressions.Expression.ValidateLambdaArgs(Type delegateType, Expression& body, ReadOnlyCollection`1 parameters)
at System.Linq.Expressions.Expression.Lambda[TDelegate](Expression body, String name, Boolean tailCall, IEnumerable`1 parameters)
at System.Linq.Expressions.Expression.Lambda[TDelegate](Expression body, Boolean tailCall, IEnumerable`1 parameters)
at System.Linq.Expressions.Expression.Lambda[TDelegate](Expression body, ParameterExpression[] parameters)
at RulesEngine.RulesHelper.CompileRuleDefinition[T](RuleDefinition`1 ruleDefinition) in c:\VSSolution\RulesEngine\RulesEngine\RulesHelper.cs:line 111
at RulesEngine.RulesHelper.CompileRuleSet[T](RuleSet`1 ruleSet) in c:\VSSolution\RulesEngine\RulesEngine\RulesHelper.cs:line 71
at UnitTestProject1.GivenIfThenElse.TestIfThenElseCondition() in c:\VSSolution\RulesEngine\UnitTestProject1\GivenIfThenElse.cs:line 34
Scyldshefing 30-Dec-16 9:02am    
I have posted the code to the GitHub @ https://github.com/Scyldshefing/RulesEngineII

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900