Jan van de Pol
Jan van de Pol

Reputation: 136

How can I make it possible to use a dynamic Lambda in Dynamic LINQ

In my search for a expression parser, I found the Dynamic LINQ API. I want to use this API for letting the end user specify some criteria for validation of the business objects.

So my first attempt to use the library succeeds with the following Unit test

var x = Expression.Parameter(typeof(int), "x");
var list = Expression.Parameter(typeof(List<int>), "list");
var e = DynamicExpression.ParseLambda(new[] { x, list }, null, "list.Any(it == x)");
var compiledExpression = e.Compile();

var myList = new List<int> { 24, 46, 67, 78 };
Assert.AreEqual(false, compiledExpression.DynamicInvoke(2, myList));
Assert.AreEqual(true, compiledExpression.DynamicInvoke(24, myList));

However I want to have a slightly more complex syntax as I want to change this

list.Any(it == x)     // OK

into

list.Any(i => i == x) // Raises error: No property or field 'i' exists in type 'int'

The second syntax would however allow me to nest lambda's (which is my ultimate goal) like so:

list1.All(i => list2.Any(j => j == i))

Anyone know how to adjust Dynamic.cs to support this syntax?

Upvotes: 3

Views: 1906

Answers (1)

Jan van de Pol
Jan van de Pol

Reputation: 136

After some hours of debugging I found the solution myself.

The following unit test succeeds now:

var list1 = Expression.Parameter(typeof(List<int>), "list1");
var list2 = Expression.Parameter(typeof(List<int>), "list2");
var e = DynamicExpression.ParseLambda(new[] { list1, list2 }, null, "list2.All(i => list1.Any(j => j == i))");
var compiledExpression = e.Compile();

var myList1 = new List<int> { 24, 46, 67, 78 };
var myList2 = new List<int> { 46 };
var myList3 = new List<int> { 8 };
Assert.AreEqual(true, compiledExpression.DynamicInvoke(myList1, myList2));
Assert.AreEqual(false, compiledExpression.DynamicInvoke(myList1, myList3));

The changes I have applied to the example Dynamic.cs file:

1) Extend the enum TokenId with the member 'Lambda'

2) Add an IDictionary named internals to class ExpressionParser. Initialize it in the ExpressionParser constructor

3) Replace (starting on line 971)

if (symbols.TryGetValue(token.text, out value) ||
    externals != null && externals.TryGetValue(token.text, out value)) {

with

if (symbols.TryGetValue(token.text, out value) ||
externals != null && externals.TryGetValue(token.text, out value) ||
internals.TryGetValue(token.text, out value)) {

4) Replace (starting on line 1151)

if (member == null)
    throw ParseError(errorPos, Res.UnknownPropertyOrField,
       id, GetTypeName(type));

with

if (member == null)
{
    if(token.id == TokenId.Lambda && it.Type == type)
    {
        // This might be an internal variable for use within a lambda expression, so store it as such
        internals.Add(id, it);
        NextToken();
        var right = ParseExpression();
        return right;
    }
    else
    {
        throw ParseError(errorPos, Res.UnknownPropertyOrField,
            id, GetTypeName(type));                        
    }
}

5) Replace (starting on line 1838)

case '=':
NextChar();
if (ch == '=') {
    NextChar();
    t = TokenId.DoubleEqual;
}
else {
    t = TokenId.Equal;
}
break;

with

case '=':
NextChar();
if (ch == '=') {
    NextChar();
    t = TokenId.DoubleEqual;
}
else if(ch == '>')
{
    NextChar();
    t = TokenId.Lambda;
}
else {
    t = TokenId.Equal;
}
break;

Upvotes: 2

Related Questions