views:

712

answers:

2

I've got a bit of a challenge where I have to create an expression tree to represent a query input by the user. Since I don't have the time to create all the possible cases of user input, I figured that expression trees would aid me in solving this.

For the most part, it has. I am, however, a bit stumped. I am in the code below trying to perform a List.Find with a dynamically created expression. The expression, in short, is this:

list.Find(m => m.ListOfStrings.Exists(s => s == "cookie"));

where m is

class MyClass
{
    public List<string> ListOfStrings { get; set; }
}

I've gotten so far as to create

s => s == "cookie"

with expressions, no problem. I've also declared a methodinfo for Exists

var existsMethod = typeof(MyClass)
        .GetProperty("ListOfStrings")
        .PropertyType
        .GetMethod("Exists");

The only problem I have is creating an expression to invoke said method with the lambda as a parameter like so

var findLambda = Expression.Lambda(
    Expression.Call(
        Expression.Property(
            Expression.Parameter(typeof(MyClass), "m"),
            typeof(MyClass).GetProperty("ListOfStrings")),
        existsMethod,
        existsLambda),
    Expression.Parameter(
        typeof (MyClass),
        "m"));

It gives an understandable exception that

Expression of type 'System.Func`2[System.String,System.Boolean]' cannot be used for parameter of type 'System.Predicate`1[System.String]' of method 'Boolean Exists(System.Predicate`1[System.String])'

How the heck can I overcome this?

Full code:

private class MyClass
{
    public List<string> ListOfStrings { get; set; }
}

public void SomeMethod()
{
    var myObject = new MyClass();
    myObject.ListOfStrings = new List<string>();
    myObject.ListOfStrings.Add("cookie");
    myObject.ListOfStrings.Add("biscuit");

    List<MyClass> list = new List<MyClass>();
    list.Add(myObject);

    var existsLambda = Expression.Lambda(
        Expression.Equal(
            Expression.Parameter(typeof(string), "s"),
            Expression.Constant("cookie")),
        Expression.Parameter(typeof(string), "s"));

    var existsMethod = typeof(MyClass).GetProperty("ListOfStrings").PropertyType.GetMethod("Exists");

    var findLambda = Expression.Lambda(
        Expression.Call(
            Expression.Property(
                Expression.Parameter(typeof(MyClass), "m"),
                typeof(MyClass).GetProperty("ListOfStrings")),
            existsMethod,
            existsLambda),
        Expression.Parameter(
            typeof (MyClass),
            "m"));

    list.Find((Predicate<MyClass>)findLambda.Compile());
}
A: 

If you look at the message, it tells you that Predicate is not compatible with Func.

Now, Predicate is defined as such:

public delegate bool Predicate<T>(
    T obj
)

and you have Func as such:

public delegate TResult Func<T, Result>(
    T arg1
)

Put together, you're trying to make these 2 delegates compatible:

public delegate bool MyClassPredicate ( MyClass obj )
public delegate bool StringFunc ( string arg1 )

Ie. string != MyClass.

I hope it made sense.

kastermester
Which doesn't have anything to do with my error, at all. =)
J. Steen
You're right, I totally managed to read that all wrong there, that said, try Bruno's suggestion.
kastermester
+1  A: 

The delegates have different types:

public delegate bool Predicate<T>(T obj);
public delegate TResult Func<T, TResult>(T arg);

The Exists method (and the Find) expect Predicate<T>. The Lambda expression compiles at runtime to Func<T, TResult>.

Try the following:

var existsLambda = Expression.Lambda(typeof(Predicate<string>), 
                Expression.Equal(
                    Expression.Parameter(typeof(string), "s"),
                    Expression.Constant("cookie")),
                Expression.Parameter(typeof(string), "s"));

(edit)

You can also use the generic Lambda Function:

var existsLambda = Expression.Lambda<Predicate<string>>(Expression.Equal(
                    Expression.Parameter(typeof(string), "s"),
                    Expression.Constant("cookie")),
                Expression.Parameter(typeof(string), "s"));
bruno conde
Yeah, I knew they were different types, I just thought it'd be smart enough to turn a Func<string, bool> into a Predicate<string>, since a predicate returns bool, after all... I'll try this when I get back to work - thanks!
J. Steen
I also know what it compiles to at runtime, the issue was that I couldn't send a Predicate<T> as a parameter, since Expression.Call expects an Expression - your code seems to solve that issue. Like I said, I'll give it a try at work tomorrow. Thanks again. =)
J. Steen
Awesome. Such a simple change. Thanks. =)
J. Steen