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

3

12

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.

Persecution answered 11/9, 2014 at 17:34 Comment(2)
Try here and hereSpancel
@DStanley There is no reason to be using reflection here. Just standard expression methods.Soundproof
R
20

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.

Rankin answered 11/9, 2014 at 17:56 Comment(7)
That makes a lot more sense Jon, I might actually be able to figure it out from there.Persecution
I can't get that to work right. It compiles, but I get a baffling runtime error. I put the code I used and the resulting exception in my question.Persecution
@DCShannon: Okay - will try to diagnose that when I get a chance.Rankin
@DCShannon: I've got a fix, but I don't rightly understand it. If you change the lambda variable to be dynamic too, it works. That feels like a bug in the C# handling of dynamic to me, but I'm not sure - I'll investigate a bit more, and mail the team.Rankin
Yep, that does it. Thanks again. Now I just have to figure out how to form that predicate.Persecution
@DCShannon: Ah, no, it's not a bug - I understand why, and will edit the answer.Rankin
@JonSkeet Awesome answer it helped me in a generic order by with Link to Entities. Kudos to you!!Dual
S
2

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"));
Soundproof answered 11/9, 2014 at 17:41 Comment(6)
I believe what you want is the standard way of using the generic, then as i stated you cant, this doesn't fulfill what he needs for sure. This an alternative to avoid what he wants.Rena
@SamerAbuRabie I have some familiarity with generics. This is actually going to go in a generic method, but there are some complexities related to the inheritance heirarchy of the objects being passed in and the way in which those objects get there that make this step necessary.Persecution
Servy, I tried your suggestion, but how do I assign the actual delegate? When I try lambda = x => true; I'm told that I "Cannot convert lambda expression to type 'System.Linq.Expressions.LambdaExpression' because it is not a delegate type"Persecution
@DCShannon: You don't do that - this is the predicate (as an expression tree). It's not clear from your question what you really need to do... if the predicate is really x => true then it's unclear why you need to call Where (as per your previous question). If the predicate is actually something else, that's relevant to the question.Rankin
@JonSkeet Oh, okay. Then I assume the Expression.Constant(true) bit is my x => true? I added some information to the question. I used that x => true predicate in the other question just because it's the simplest possible one I could think of.Persecution
@DCShannon: So what do you know about the type if you're going to have it in a lambda expression? That's the tricky bit - you clearly know some stuff statically.Rankin
R
-2

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.

Rena answered 11/9, 2014 at 17:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.