Javran
Javran

Reputation: 3434

can I have a nested lambda expression stored in a Dictionary?

I'd like to parse a string into a lambda expression in C#, for example, parse "lt 5" to x => x < 5 so I can use it as the argument of Enumerable.Where:

static void Main(string[] args)
{
    Enumerable
        .Range(0,10)
        .Select(x => (double)x)
        .Where(Parse("lt 5"))
        .ToList()
        .ForEach(System.Console.WriteLine);
}

private const List<Tuple<List<string>, Func<double, Func<double, bool>>>> operationList =
    new Dictionary<string, Func<double, Func<double, bool>>>()
    { { "lt <",     val => ( x => x <  val ) }
    , { "le <=",    val => ( x => x <= val ) }
    , { "eq = ==" , val => ( x => x == val ) }
    , { "ne != <>", val => ( x => x != val ) }
    , { "ge >=",    val => ( x => x >= val ) }
    , { "gt >",     val => ( x => x >  val ) } }
    .Select(kv =>
        new Tuple<
            List<string>,
            Func<double, Func<double, bool>>>(
                kv.Key.Split(' ').ToList(),
                kv.Value))
    .ToList();

public static Func<double, bool> Parse(string raw)
{
    var fields = raw.Split(' ');
    var predKey = fields[0].ToLower();

    var predBuilder = operationList.FirstOrDefault(tp =>
        tp.Item1.FirstOrDefault(key => key.Equals(predKey)) != null);

    return predBuilder.Item2(double.Parse(fields[1]));
}

First I made a operationList that stores a tuple where Item1 describes all aliases to the operator and Item2 stores a curried version of the operator.

Then Parse will split string lt 5 into ["lt","5"] and search the operation according to key lt.

Finally, if I can find the operation, 5 will be partial applied to it and the result will be a lambda expression x => x < val.

However, the compiler was mad at { "lt <", val => ( x => x < val ) } and complained "Expression cannot contain anonymous methods or lambda expressions".

I have no idea about what it means and how to get things work.

Upvotes: 0

Views: 539

Answers (1)

Ilya Ivanov
Ilya Ivanov

Reputation: 23636

change const to readonly and make it static, like so:

private readonly static List<Tuple<List<string>, Func<double, Func<double, bool>>>> operationList =
    new Dictionary<string, Func<double, Func<double, bool>>>()
    { { "lt <",     val => ( x => x <  val ) }
    , { "le <=",    val => ( x => x <= val ) }
    , { "eq = ==" , val => ( x => x == val ) }
    , { "ne != <>", val => ( x => x != val ) }
    , { "ge >=",    val => ( x => x >= val ) }
    , { "gt >",     val => ( x => x >  val ) } }
    .Select(kv =>
        new Tuple<
            List<string>,
            Func<double, Func<double, bool>>>(
                kv.Key.Split(' ').ToList(),
                kv.Value))
    .ToList();

In C# const keyword is used to denote compile time constants, but in your example list is calculated at runtime using LINQ.

In C# const values are always static and making it readonly will change field's scope to instance.

Note, that contents of you list might change during execution and readonly won't stop you from removing and adding Tuples into a list. Consider using ReadOnlyCollection (it's still won't stop you from modifying it, just will make it harder) or using Immutable Collections (which aren't part of .net BCL)

Upvotes: 1

Related Questions