Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How Do I Create an Expression<Func<>> with Type Parameters from a Type Variable

I'd like to write a statement like the following:

Expression<Func<AClass, bool>> filter = x => true;

Except instead of AClass, I'd like to use a Type variable determined at runtime. So, something conceptually like this:

Type aClassType = methodParameter.GetType();
Expression<Func<aClassType, bool>> filter = x => true;

Obviously the syntax will be quite a bit different. I'm assuming I'll need to use some sort of reflection or other fanciness.

End Goal

The end goal here is a bit complex, so I've simplified things immensely for the above example. The actual .Where call that is going to use this delegate looks more like this:

var copy = iQ;

...

copy = copy.Where( p1 => iQ.Where( p2 => pgr2.Key == p1.Key && p2.DataField == column.DataField && p2.ColumnText.Contains( requestValue ) ).Any() );

All of the properties of p1 and p2 are properties of a parent class of the element type of the IQueryable iQ. The Type variable I'd like to create will be the actual element type of iQ, i.e. the child class.

How do I do it?

Current Progress

Based on the answers below, I've written this test code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace IQueryableWhereTypeChange {
    class Program {
        static void Main( string[] args ) {
            var ints = new List<ChildQueryElement>();

            for( int i = 0; i < 10; i++ ) {
                ints.Add( new ChildQueryElement() { Num = i, Value = i.ToString() } );
            }

            IQueryable<ChildQueryElement> theIQ = ints.AsQueryable();

            Type type = typeof(ChildQueryElement);
            var body = Expression.Constant(true);
            var parameter = Expression.Parameter(type);
            var delegateType = typeof(Func<,>).MakeGenericType(type, typeof(Boolean));
            var lambda = Expression.Lambda( delegateType, body, parameter );

            Console.WriteLine( lambda.GetType() );

            dynamic copy = theIQ;

            Type copyType1 = copy.GetType().GetGenericArguments()[ 0 ];
            Type elementType1 = ((IQueryable)copy).ElementType;
            Console.WriteLine( "copyType1 : " + copyType1.ToString() );
            Console.WriteLine( "elementType1 : " + elementType1.ToString() );

            copy = Queryable.Where( copy, lambda );

            Type copyType2 = copy.GetType().GetGenericArguments()[ 0 ];
            Type elementType2 = ((IQueryable)copy).ElementType;
            Console.WriteLine( "copyType2 : " + copyType2.ToString() );
            Console.WriteLine( "elementType2 : " + elementType2.ToString() );
        }
    }

    public class ParentQueryElement {
        public int Num { get; set; }
    }
    public class ChildQueryElement : ParentQueryElement {
        public string Value { get; set; }
    }
}

That program has this output:

System.Linq.Expressions.Expression`1[System.Func`2[IQueryableWhereTypeChange.ChildQueryElement,System.Boolean]]  
copyType1 : IQueryableWhereTypeChange.ChildQueryElement  
elementType1 : IQueryableWhereTypeChange.ChildQueryElement  

Unhandled Exception:  

    Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: 
        The best overloaded method match for 
            'System.Linq.Queryable.Where<IQueryableWhereTypeChange.ChildQueryElement>(
                System.Linq.IQueryable<IQueryableWhereTypeChange.ChildQueryElement>,
                System.Linq.Expressions.Expression<System.Func<IQueryableWhereTypeChange.ChildQueryElement,bool>>
            )' 
        has some invalid arguments
   at CallSite.Target(Closure , CallSite , Type , Object , LambdaExpression )
   at System.Dynamic.UpdateDelegates.UpdateAndExecute3[T0,T1,T2,TRet](CallSite site, T0 arg0, T1 arg1, T2 arg2)
   at IQueryableWhereTypeChange.Program.Main(String[] args)

I'd like to get this working before I try and replicate my complicated predicate.

I find the exception rather baffling, as the output for lambda.GetType() matches the type in the exception almost exactly. The only difference is System.Boolean versus bool, but that shouldn't matter.

like image 725
DCShannon Avatar asked Sep 11 '14 17:09

DCShannon


People also ask

What is the difference between expression and Func C#?

An Expression<Func<>> is the representation of a function that has yet to be turned into code. A Func<> is an actual executable function. Using the former allows you to turn the expression into the appropriate function at the time that it is invoked.

What type allows the C# compiler to build an expression tree from code?

The C# compiler can generate expression trees only from expression lambdas (or single-line lambdas). It cannot parse statement lambdas (or multi-line lambdas). For more information about lambda expressions in C#, see Lambda Expressions.

How to write lambda expression in C#?

To create a lambda expression, you specify input parameters (if any) on the left side of the lambda operator and an expression or a statement block on the other side. When you use method-based syntax to call the Enumerable. Select method in the System.


2 Answers

You need to create the appropriate delegate type, then pass that to the Expression.Lambda method. For example:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

class Test
{
    static void Main()
    {
        var type = typeof(string);
        var body = Expression.Constant(true);
        var parameter = Expression.Parameter(type);
        var delegateType = typeof(Func<,>).MakeGenericType(type, typeof(bool));
        dynamic lambda = Expression.Lambda(delegateType, body, parameter);
        Console.WriteLine(lambda.GetType()); // Expression<string, bool>
    }
}

Now of course your body would normally not just be a constant - but we don't know what you need to do there. From your edited question, it looks like you do have some statically typed knowledge of the type, otherwise you wouldn't be able to express that lambda expression. So either you need to build up the expression tree manually to be the equivalent of the lambda expression (using Expression.Property etc) or create one expression tree from what you know, and then use an expression tree visitor to adapt that to the actual type.

EDIT: Note that the type of lambda must be dynamic for it to work as the second argument to Queryable.Where, otherwise the execution-time version of the C# compiler will use the static type of the variable (which would just be LambdaExpression) to constrain overload resolution. You want it to match the actual Expression<TDelegate> type, which you can't express at compile-time, so you need to use dynamic.

like image 60
Jon Skeet Avatar answered Oct 03 '22 22:10

Jon Skeet


You'll need to create an expression with a body set to the constant true and a parameter of the type of your type. There is an Expression to do each of these things:

Type type = typeof(object);
var lambda = Expression.Lambda(
    Expression.Constant(true), 
    Expression.Parameter(type, "parameter"));
like image 33
Servy Avatar answered Oct 04 '22 00:10

Servy