0

I have a delegate as follows:

public delegate TestResult TestCase(byte[] source);

...where the return TestResult is as follows:

public class TestResult {

    public bool Pass { get; }
    public int Index { get; }

    public TestResult(bool result, int index) {
        Pass = result;
        Index = index;
    }
}

An example TestCase delegate looks like:

public static TestResult In(byte[] tkn, ITestSet testSet) {
    return testSet.Contains(tkn);
}

ITestSet is not much more than an encapsulated HashSet<byte[]>.

In one use case I have 2 test sets: (1) A - z, (2) 0 - 9. I want to test if an input byte[] is in either test set.

I am using Expression<TestCase> but having trouble figuring out how to implement the Or test case. I have a TestCaseBuilder with the following methods:

public class TestCaseBuilder {
    private Expression<TestCase> tcExpr;       

    public TestCaseBuilder With(TestCaseBuilder tcBuilder) {
        tcExpr = tcBuilder.tcExpr;
        return this;
    }

    public TestCaseBuilder Or(TestCaseBuilder tcBuilder) {
        tcExpr = tcExpr.Or(tcBuilder.tcExpr);        
        return this;
    }
}

...and my extension method:

public static Expression<TestCase> Or (this Expression<TestCase> tcLeft, Expression<TestCase> tcRight) {
    var lExpr = (LambdaExpression)tcLeft;
    var rExpr = (LambdaExpression)tcRight;    
    var param = lExpr.Parameters;

    return Expression.Lambda<TestCase>(/* what to do here ? */, param);
}

Expression.OrElse is mechanically what I would think is appropriate but cannot use that since I am returning a TestResult, not a bool.

Here is how the TestCaseBuilder is used:

testcaseBuilder.As("Foo")
    .With(isLetterTestCase)
    .Or(isDigitTestCase);

I have performed the Or using just the TestCase delegates:

public static TestCase Or(this TestCase tc1, TestCase tc2) {
    return tkn => {
        var res = tc1(tkn);
        if (res.Pass) {
            return res;
        }

        return tc2(tkn);
    };
}

How can I combine the 2 Expression<TestCase> in a custom Or method without invoking the first test case?

IAbstract
  • 19,551
  • 15
  • 98
  • 146
  • I think you'll need a custom ExpressionVisitor that is supplied the parameters to return upon visiting both expressions, returning a new one – pinkfloydx33 Feb 25 '17 at 21:28
  • @pinkfloydx33: may very well be ... I haven't used `ExpressionVisitor` yet so maybe someone will be able to provide a solution with that. This particular *test case* is just the tip of the iceberg. – IAbstract Feb 25 '17 at 22:33
  • It's very easy to write one. I'm on my cell phone as always so it's hard to write one. If you do a search for ParameterReplacer or ExpressionParamaterReplacer there is a post by Marc Gravell showing the usage. – pinkfloydx33 Feb 25 '17 at 22:35
  • Not the one I was thinking about, but there's an implementation here http://stackoverflow.com/a/11160067/491907 I think you should only need to override `VisitParameter` in your case, but I might be wrong without actually trying it – pinkfloydx33 Feb 25 '17 at 22:36
  • See Marc's answer here at the bottom of his post http://stackoverflow.com/questions/457316/combining-two-expressions-expressionfunct-bool – pinkfloydx33 Feb 25 '17 at 22:42

1 Answers1

0

Is this what you wanted

        public static Expression<Func<byte[], TestResult, TestCase, TestResult>> helperExp = (inp, res, next) => res.Pass ? next(inp) : res;

        public static Expression<TestCase> Or(Expression<TestCase> exp1, Expression<TestCase> exp2)
        {
            var param = exp1.Parameters;

            Expression<TestCase> or = Expression.Lambda<TestCase>(
               Expression.Invoke(helperExp,
                param[0], Expression.Invoke(exp1, param), exp2),param);

            return or;
        }

With a block expression no invoke

public static Expression<TestCase> Or(Expression<TestCase> exp1, Expression<TestCase> exp2)
        {
            var param = exp1.Parameters;

            ParameterExpression local = Expression.Parameter(typeof(TestResult), "local");

            BlockExpression block = Expression.Block(
                new[] { local },
                Expression.Assign(local, exp1.Body),
                Expression.Condition(Expression.Property(local, nameof(TestResult.Pass)), exp2.Body, local));

            return Expression.Lambda<TestCase>(block, param);
        }

Test

            Expression<TestCase> exp1 = (tc) => new TestResult(true);
            Expression<TestCase> exp2 = (tc) => new TestResult(false);

            var first = Or(exp1, exp1);

            var second = Or(first, exp2);

            var func = second.Compile();

            var result = func(new byte[] { });

There might be a better way to do a conditional monad without expressions using https://github.com/louthy/csharp-monad I thing .net core uses the monad principle for middleware.

Filip Cordas
  • 2,531
  • 1
  • 12
  • 23
  • Without `Expression.Invoke` is the requirement. – IAbstract Feb 26 '17 at 01:15
  • 1
    @IAbstract But how can you know the result if you don't do an Invoke at some point? – Filip Cordas Feb 26 '17 at 01:28
  • 1
    @IAbstract made an edit to remove the invoke if it is still not what you wanted leave a comment and I will remove the answer. – Filip Cordas Feb 26 '17 at 02:46
  • @FilipCordas: I didn't want to invoke the expression when building the `Or` condition because there is no input at that point. The expression is built based on instructions for later input. Once the instruction is complete, the `Expression` will be compiled. Then later, input will be provided which the expression can test. Make sense? – IAbstract Feb 26 '17 at 13:26
  • @FilipCordas: great start and looks promising but there are two things - (1) it looks like this is actually an `And` evaluation since the `Condition` (on true) will go directly to the next evaluation rather than returning the `TestResult`. (2) I am getting an InvalidOperationException: **Additional information: variable 't' of type 'System.Byte[]' referenced from scope '', but it is not defined**. I am trying to track this down now. – IAbstract Feb 26 '17 at 14:08
  • Okay, for the exception, looks like I need to back up to my base expressions and build those rather than using a lambda: http://stackoverflow.com/a/30556993/210709 – IAbstract Feb 26 '17 at 14:31
  • You are right this will do an and just switch the condition Expression.Condition(Expression.Property(local, nameof(TestResult.Pass)), local, exp1.Body) – Filip Cordas Feb 26 '17 at 14:55
  • 1
    @IAbstract In regards to input parametars the delegate has a fix set of input prametars with a set of types I don't see how you could change the input parametas with out recreating whole set of expressions. Using Invoke makes no difference. Well I hope I helped a bit, if you find the whole solution could you post the answer since I am interested in how you did it. – Filip Cordas Feb 26 '17 at 15:05
  • I will. It looks like I was successful in creating the base Expression that replaces `t => t.In(testset)` delegate. When the expression is compiled, I have a compatible delegate `TestCase` which passes first unit tests. Once I've incorporated this answer with passing tests I'll post a solution with changes I had to make. – IAbstract Feb 26 '17 at 15:20
  • 1
    In case you need to reparametrize the second expression with the parameter from the first one, this [answer](http://stackoverflow.com/a/42041916/6996876) of mine could help maybe.... –  Feb 26 '17 at 17:18