如何将C#中的MemberExpression实例组合为LambdaExpression

本文关键字:实例 组合 LambdaExpression MemberExpression 中的 | 更新日期: 2023-09-27 18:22:12

给定这样的类:

public class AnEntity
{
    public int prop1 { get; set; }
    public string prop2 { get; set; }
    public string prop3 { get; set; }
}

我能够生成一个lambda表达式,它选择一个这样的属性:

ParameterExpression pe = Expression.Parameter(typeof(AnEntity), "x");
MemberExpression selectClause = Expression
    .MakeMemberExpression(
        pe, 
        typeof(AnEntity).GetProperty(prop2)); // selecting prop2
var selectLambda = Expression.Lambda<Func<AnEntity, object>>(selectClause, pe);

然后我可以使用这样的lambda表达式:

IQueryable<AnEntity> myEntities = dbContext.MyEntities.AsQueryable();
var results = myEntities.Select(selectLambda);

如何在selectLambda中添加第二个select子句?例如,我将如何选择prop2和prop3?

如何将C#中的MemberExpression实例组合为LambdaExpression

下面是一个充实的例子;usr";在他的使用MemberInitExpression的解决方案中描述。

对于我的解决方案,我将为您提供一个新的类,您将希望将表达式结果写入该类。事实上,这可能是实体本身的POCO,但通过指定不同的类,与您投影的类相比,您投影到的类更清楚。作为";usr";如前所述,您也可以尝试使用Tuple或其他构造。我目前最喜欢的是使用我附加在底部的额外代码来动态创建一个新类型。这比Tuple更灵活一点,但它也有一些缺点,因为它需要反射来访问它。

要投影到的类:

public class Holder
{
    public int Item1{ get; set; }
    public string Item2 { get; set; }
    public string Item3 { get; set; }
}

创建表达式的代码:

ParameterExpression paramExp = Expression.Parameter(typeof(AnEntity));
NewExpression newHolder = Expression.New(typeof(Holder));    
Type anonType = typeof(Holder);                
MemberInfo item1Member = anonType.GetMember("Item1")[0];
MemberInfo item2Member = anonType.GetMember("Item2")[0];
MemberInfo item3Member = anonType.GetMember("Item3")[0];
// Create a MemberBinding object for each member 
// that you want to initialize.
 MemberBinding item1MemberBinding =
 Expression.Bind(
     item1Member,
     Expression.PropertyOrField(paramExp, "prop1"));
 MemberBinding item2MemberBinding =
 Expression.Bind(
     item2Member,
     Expression.PropertyOrField(paramExp, "prop2"));
 MemberBinding item3MemberBinding =
 Expression.Bind(
     item3Member,
     Expression.PropertyOrField(paramExp, "prop3"));
// Create a MemberInitExpression that represents initializing 
// two members of the 'Animal' class.
MemberInitExpression memberInitExpression =
    Expression.MemberInit(
        newHolder,
        item1MemberBinding,
        item2MemberBinding,
        item3MemberBinding);
var lambda = Expression.Lambda<Func<AnEntity, Holder>>(memberInitExpression, paramExp);

最后,如何调用表达式:

IQueryable<AnEntity> myEntities = dbContext.MyEntities.AsQueryable();
var results = myEntities.Select(selectLambda);

如果您想动态定义返回值的类型,下面是一些附加代码:

    public static Type CreateNewType(string assemblyName, string typeName, params Type[] types)
    {
        // Let's start by creating a new assembly
        AssemblyName dynamicAssemblyName = new AssemblyName(assemblyName);
        AssemblyBuilder dynamicAssembly = AssemblyBuilder.DefineDynamicAssembly(dynamicAssemblyName, AssemblyBuilderAccess.Run);
        ModuleBuilder dynamicModule = dynamicAssembly.DefineDynamicModule(assemblyName);
        // Now let's build a new type
        TypeBuilder dynamicAnonymousType = dynamicModule.DefineType(typeName, TypeAttributes.Public);
        // Let's add some fields to the type.
        int itemNo = 1;
        foreach (Type type in types)
        {
            dynamicAnonymousType.DefineField("Item" + itemNo++, type, FieldAttributes.Public);
        }
        // Return the type to the caller
        return dynamicAnonymousType.CreateType();
    }

回答此类问题的方法是在强类型C#(select new { x.p1, x.p2 })中编写要构建的模式,并使用调试器查看表达式树。然后你自己造那棵树。

您将发现一个MemberInitExpression实例化了一个具有两个属性p1p2的类,这两个属性是从x初始化的。

你必须以某种方式提供这样一个班级。要么自己定义它(class X { string p1, p2; }),要么使用类似Tuple的构造。或者object[]

要明白,您只能有一个返回值。因此,它需要封装多个值。

最简单的方法是使用object[]。看看C#编译器是如何做到的。

没有"第二次选择返回值"。您可以返回一个值,该值可能是一个聚合值。

C#语言以LINQ和匿名类型的形式在这方面提供了大量的语法优势。为了通过表达式树构建的lambda返回聚合,您必须创建一个类型来保存所有不同的值(就像C#编译器在后台看到匿名类型时所做的那样),然后调用其构造函数来传递您想要返回的多个值(这实际上比C#在后台所做的更容易,后者称为一堆属性设置器)。在运行时,不存在匿名类型。它们都会被命名,如果不是由程序员命名,那么就是由编译器命名。

事实上,您可能可以使用Tuple<Type1, Type2, Type3>,而不是专门为此目的创建类。但是,您的成员属性将不会被很好地命名。