Enumerable Select by Expression Tree

6.1k views Asked by At

I'm studying "Expression Tree" but I'm not managing to perform these expressions:

// first case
someList.Select(p => p.SomeProperty);

and

// second case
someList.Select(p => new OtherClass 
{
    SomeProperty = p.SomeProperty
})

To the "first case" I tried do this:

var someList = new List<SomeClass>();
someList.Add(new SomeClass { SomeProperty = "Hello" });

var someParam = Expression.Parameter(typeof(SomeClass), "p");
var someProperty = Expression.Property(someParam, "SomeProperty");

Expression.Call(
    typeof(Enumerable),
    "Select",
    new Type[]
    {
        typeof(SomeClass),
        typeof(string)
    },
    Expression.Lambda(
        someProperty,
        someParam
    )
).Dump();

But I get this error:

InvalidOperationException: No generic method 'Select' on type 'System.Linq.Enumerable' is compatible with the supplied type arguments and arguments. No type arguments should be provided if the method is non-generic.

About the "second case", I don't have ideia how to proceed.

Can anyone guide me here?

2

There are 2 answers

0
Alexandre Perez On BEST ANSWER

Calm down folks, after some research I found what was missing in my code...

On the fist case:

Expression.Call(
    typeof(Enumerable),
    "Select",
    new Type[]
    {
        typeof(SomeClass),
        typeof(string)
    },
    Expression.Constant(someList), // <---------------- HERE IT IS
    Expression.Lambda(
        someProperty,
        someParam
    )
);

To the second case, I created the "new" expression through the code below:

var bind = Expression.Bind(typeof(OtherClass).GetProperty("SomeProperty"), someProperty);
var otherClassNew = Expression.New(typeof(OtherClass));
var otherClassInit = Expression.MemberInit(otherClassNew, bind);

Anyway, Thank you all for your help!

10
xanatos On

Some examples of what you could do:

Given

public class SomeClass
{
    public string SomeProperty { get; set; }
}

and

var someList = new List<SomeClass>();
someList.Add(new SomeClass { SomeProperty = "Hello" });

var someParam = Expression.Parameter(typeof(SomeClass), "p");
var someProperty = Expression.Property(someParam, "SomeProperty");

Expression<Func<SomeClass, string>> lambda = Expression.Lambda<Func<SomeClass, string>>(someProperty, someParam); // p => p.SomeProperty

Using an IEnumerable<SomeClass>... Note the .Compile()

Func<SomeClass, string> compiled = lambda.Compile();
IEnumerable<string> q1 = someList.Select(compiled);

You shouldn't ever use AsQueryable() but in unit tests and "experimentation" programs (like this one). Just to make @Peter happy, I'll add another possible condition: if you really know what it does (not think you know what it does, really!), then you can use it. But if it the first time you use it, I still suggest you ask on SO if you are right in using it.

IQueryable<SomeClass> queryable = someList.AsQueryable();

Directly using the Queryable.Select()

IQueryable<string> q2 = queryable.Select(lambda);

Building a Select and using the CreateQuery (this is very similar to what internally the Queryable.Select does) to "inject" it in the query.

MethodInfo select = (from x in typeof(Queryable).GetMethods()
                    where x.Name == "Select" && x.IsGenericMethod
                    let gens = x.GetGenericArguments()
                    where gens.Length == 2
                    let pars = x.GetParameters()
                    where pars.Length == 2 && 
                        pars[0].ParameterType == typeof(IQueryable<>).MakeGenericType(gens[0]) &&
                        pars[1].ParameterType == typeof(Expression<>).MakeGenericType(typeof(Func<,>).MakeGenericType(gens))
                    select x).Single().MakeGenericMethod(typeof(SomeClass), typeof(string));

MethodCallExpression select2 = Expression.Call(null, select, Expression.Constant(queryable), lambda);

IQueryProvider provider = queryable.Provider;
IQueryable<string> q3 = provider.CreateQuery<string>(select2);