【问题标题】:How Do I Create an Expression<Func<>> with Type Parameters from a Type Variable如何使用类型变量的类型参数创建表达式<Func<>>
【发布时间】:2014-09-11 17:34:45
【问题描述】:

我想写如下声明:

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的所有属性都是IQueryableiQ元素类型的父类的属性。我要创建的 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,但这无关紧要。

【问题讨论】:

  • 试试herehere
  • @DStanley 没有理由在这里使用反射。只是标准的表达方法。
  • 真的,投反对票吗?为了什么?

标签: c# linq types expression


【解决方案1】:

您需要创建适当的委托类型,然后将其传递给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&lt;TDelegate&gt; 类型匹配,而您无法在编译时表示,因此您需要使用 dynamic

【讨论】:

  • 这更有意义,乔恩,我实际上可以从那里弄清楚。
  • 我无法让它正常工作。它编译,但我得到一个莫名其妙的运行时错误。我把我使用的代码和产生的异常放在我的问题中。
  • @DCShannon:好的,我会在有机会时尝试诊断。
  • @DCShannon:我有一个修复,但我没有正确理解它。如果您也将lambda 变量更改为dynamic,它可以工作。 感觉对我来说就像是 C# 处理 dynamic 中的一个错误,但我不确定 - 我会进行更多调查,然后给团队发邮件。
  • 是的,就是这样。再次感谢。现在我只需要弄清楚如何形成那个谓词。
【解决方案2】:

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

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

【讨论】:

  • 我相信你想要的是使用泛型的标准方式,那么正如我所说的你不能,这并不能满足他的需求。这是避免他想要的东西的替代方法。
  • @SamerAbuRabie 我对泛型有些熟悉。这实际上是在一个通用方法中进行的,但是有一些与传入对象的继承层次结构以及这些对象到达那里的方式相关的复杂性使得这一步是必要的。
  • Servy,我尝试了您的建议,但如何分配实际代表?当我尝试lambda = x =&gt; true; 时,我被告知“无法将 lambda 表达式转换为类型 'System.Linq.Expressions.LambdaExpression',因为它不是委托类型”
  • @DCShannon:你不要那样做——这谓词(作为表达式树)。从你的问题中不清楚你真正需要做什么......如果谓词真的是x =&gt; true,那么不清楚为什么你需要调用Where(根据你之前的问题)。如果谓词实际上是别的东西,那就与问题有关。
  • @JonSkeet 哦,好的。然后我假设Expression.Constant(true) 位是我的x =&gt; true?我在问题中添加了一些信息。我在另一个问题中使用了 x =&gt; true 谓词,只是因为它是我能想到的最简单的谓词。
【解决方案3】:

您不能将 Type 作为泛型参数传递,这不是泛型的想法,您想要的只是方法的参数。

【讨论】:

    猜你喜欢
    • 2023-01-04
    • 1970-01-01
    • 1970-01-01
    • 2023-04-10
    • 2021-02-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多