Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

LINQ: How to force a value based reference?

Tags:

c#

.net

linq

I want to provide a set of filters for a user to pick from, and each filter will correspond to an Expression<Func<X, bool>>. So, I might want to take a dynamic list of available items ('Joe', 'Steve', 'Pete', etc), and create a collection of "hard-coded" filters based on those names, and let the user select which filter(s) he wants to use. My problem is that even when I try to "hard-code" my expression based on a string value from the dynamic list, the expression is still storing the value as, what looks to me, a property hanging off of an anonymous type (and I don't know how to serialize the anon. type). Sorry if this is confusing, I'm not quite sure how to articulate this.

Here's my sample code:

    public class Foo
    {
        public string Name { get; set; }
    }
    static void Main(string[] args)
    {
        Foo[] source = new Foo[]
            {
                new Foo() { Name = "Steven" } ,
                new Foo() { Name = "John" } ,
                new Foo() { Name = "Pete" },
            };

            List<Expression<Func<Foo, bool>>> filterLst = new List<Expression<Func<Foo, bool>>>();
            foreach (Foo f in source)
            {
                Expression<Func<Foo, bool>> exp = x => x.Name == f.Name;
                filterLst.Add(exp);
            }
    }
}

My problem is that when I look at when I look at the body of my expression, it reads as follows:

(x.Name = value(ConsoleApplication1.Program+<>c__DisplayClass3).value)

When what I really want is for the first one to read like this:

(x.Name = "Steven")

(if I change my code to this, instead, that's exactly what I get:

      Expression<Func<Foo, bool>> exp = x => x.Name == "Steven";

)

I've tried forcing my value to a local string value before sticking it into the Expression, but it doesn't seem to help:

    List<Expression<Func<Foo, bool>>> filterLst = new List<Expression<Func<Foo, bool>>>();
    foreach (Foo f in source)
    {
        string value = f.Name;
        Expression<Func<Foo, bool>> exp = x => x.Name == value;
        filterLst.Add(exp);
    }

I don't understand why (or really even how) it's still looking at some anonymous type even once I'm using a local variable that is declared to a string. Is there a way to make this work as I want it to?

like image 436
Steven Avatar asked Nov 30 '10 22:11

Steven


2 Answers

The anon-type is actually the compiler-generated type it is using to perform capture of the variables. With delegates you can hack around this by implementing the capture by hand, but not with lambda expressions compiled to expression-trees.

Two choices:

  • build the expression tree explicitely on code via Expression.Constant etc
  • learn how to handle the anon types

The latter isn't too bad actually; they are just MemberExpression typically, although I have some code kicking around that covers this in full detail. I can also provide examples of buildin the expression tree, but I'm not at a PC at the moment and it doesn't lend itself well to iPod typing...

From a brief read of the question I'd look at the first option more than the second.

Oh, and watch out; the first foreach code in the question looks susceptible to the notorious l-value capture issue ;)


Edit: I found a PC ;p

        var param = Expression.Parameter(typeof(Foo), "x");
        var body = Expression.Equal(
            Expression.PropertyOrField(param, "Name"),
            Expression.Constant(f.Name, typeof(string)));

        var exp = Expression.Lambda<Func<Foo, bool>>(body, param);
        filterLst.Add(exp);
like image 105
Marc Gravell Avatar answered Nov 02 '22 01:11

Marc Gravell


Marc Gravell's answer is correct, and here's how you'd implement his first choice:

var filters = 
    from f in source
    let param = Expression.Parameter(typeof(Foo),"x")
    select Expression.Lambda<Func<Foo, bool>>(
        Expression.Equal(
            Expression.Property(param, "Name"),
            Expression.Constant(f.Name)), param);

But this typically isn't necessary. Your second example of the for loop should work with all major LINQ providers. Is there a reason you need the expression to use constants?

like image 38
StriplingWarrior Avatar answered Nov 02 '22 03:11

StriplingWarrior