如何创建表达式<func <>&gt;使用类型变量</func <>中的类型参数

时间:2014-09-11 17:34:45

标签: c# linq types expression

我想写一个如下声明:

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

除了代替AClass,我想使用在运行时确定的Type变量。所以,概念上是这样的:

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

显然语法会有所不同。我假设我需要使用某种反思或其他幻想。

结束目标

这里的最终目标有点复杂,所以我为上面的例子极大地简化了事情。要使用此委托的实际.Where调用看起来更像是这样:

var copy = iQ;

...

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

p1和p2的所有属性都是IQueryable iQ的元素类型的父类的属性。我想要创建的Type变量将是iQ的实际元素类型,即子类。

我该怎么做?

当前进展

根据以下答案,我写了这个测试代码:

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; }
    }
}

该程序有此输出:

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)

在尝试复制复杂的谓词之前,我想让它工作。

我发现异常令人困惑,因为lambda.GetType()的输出几乎完全匹配异常中的类型。唯一的区别是System.Booleanbool,但这无关紧要。

3 个答案:

答案 0 :(得分:13)

您需要创建适当的委托类型,然后将其传递给Expression.Lambda方法。例如:

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>
    }
}

当然,你的body通常不仅仅是一个常数 - 但我们不知道你需要做什么。从您编辑过的问题看来,您确实拥有某些静态类型的类型知识,否则您将无法表达该lambda表达式。因此,您需要手动构建表达式树以等同于lambda表达式(使用Expression.Property等)或者根据您所知道的方式创建一个表达式树,然后使用表达式树访问者将其调整为实际类型。

编辑:请注意,lambda的类型必须为dynamic才能使其作为Queryable.Where的第二个参数,否则C#编译器的执行时版本将使用变量的静态类型(只是LambdaExpression)来约束重载决策。您希望它与实际Expression<TDelegate>类型匹配,您无法在编译时表达,因此您需要使用dynamic

答案 1 :(得分:1)

您需要创建一个表达式,其主体设置为常量true,并且您需要创建类型类型的参数。有Expression来做这些事情:

Type type = typeof(object);
var lambda = Expression.Lambda(
    Expression.Constant(true), 
    Expression.Parameter(type, "parameter"));

答案 2 :(得分:-1)

你不能将Type作为泛型参数传递,这不是泛型的概念,你想要的只是一个方法的参数。