DCShannon
DCShannon

Reputation: 2614

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.

Upvotes: 12

Views: 15711

Answers (3)

Jon Skeet
Jon Skeet

Reputation: 1500575

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.

Upvotes: 20

Servy
Servy

Reputation: 203829

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"));

Upvotes: 2

Samer Aburabie
Samer Aburabie

Reputation: 256

You cant pass the Type as ageneric parameter, this is not the idea of a generic, what you want is simply a parameter to a method for instance.

Upvotes: -2

Related Questions