在运行时创建一个带有新匿名类型的lambda表达式

7
我想调用一个期望像这样传递参数的方法:
Expression<Func<sometype, 'a>> expr

我需要在运行时构建这个参数,因为在此之前我不知道匿名类型会是什么样子的;它可能具有任意数量的字段:

x => new { a=x.a, b=x.b, c=x.c, etc... }

我可以在运行时创建一个类型,该类型具有与所需匿名类型相同的“签名”(这是正确的单词吗?),但问题是:我如何从中构造此lambda表达式?特别是Expression.New让我感到困扰,因为我需要将ConstructorInfo传递给它,而我必须从现有类型(可以是匿名类型,但我无法在运行时创建匿名类型。或者有没有办法做到这一点?)中获取它。
更新(根据评论请求提供一些上下文)
我要调用的方法是:
DependentNavigationPropertyConfiguration.HasForeignKey<TKey>(Expression<Func<TDependentEntityType, TKey>> foreignKeyExpression)

我想要这样做的原因是,自动将继承于某个基类的实体的导航属性包含该基类的键作为外键。由于一个实体可以有多个任意类型的键字段,所以TKey类型只能在运行时确定。

你的场景输入是什么?你是在运行时从字符串数据构建它吗? - Ilya Ivanov
在运行时,从“sometype”中选择了一些字段。我需要从中构建表达式。匿名类型的字段名称与从“sometype”中选择的字段名称相同。 - EPLKleijntjens
为什么会有负评?请解释一下,让我能够学到东西。 - EPLKleijntjens
3个回答

16

使用单独的方法:

public static void Main()
{
    var myExpression = Express(str => new { 
        String = str, 
        Length = str.Length 
    });

    // We can compile/use it as well...
    var compiledExpression = myExpression.Compile();
    var anonymousOutput = compiledExpression("Input String");

    Console.WriteLine(anonymousOutput.String); // Output: Input String
    Console.WriteLine(anonymousOutput.Length); // Output: 12

    Debug.WriteLine(myExpression); // Output: "str => new <>f__AnonymousType0`2(String = str, Length = str.Length)"
    Console.ReadLine();
}


static Expression<Func<String, T>> Express<T>(Expression<Func<String, T>> expression)
{
    return expression;
}

请注意,起始类型(在我的例子中是String)必须事先知道。

更新:

由于你似乎想动态创建一个类型,我将为你提供一个如何做到这一点的简单示例。

public static void Main()
{
        // Create an anonymous type with two fields
    Type myAnonymousType = CreateNewType<String, Int32>();
    dynamic myAnon = Activator.CreateInstance(myAnonymousType);

    myAnon.FieldA = "A String";
    myAnon.FieldB = 1234;


    Console.WriteLine(myAnon.FieldA); // Output : "AString"
    Console.WriteLine(myAnon.FieldB); // Output : 1234
    Console.ReadLine();
}

public static Type CreateNewType<TFieldTypeA, TFieldTypeB>()
{
    // Let's start by creating a new assembly
    AssemblyName dynamicAssemblyName = new AssemblyName("MyAsm");
    AssemblyBuilder dynamicAssembly = AssemblyBuilder.DefineDynamicAssembly(dynamicAssemblyName, AssemblyBuilderAccess.Run);
    ModuleBuilder dynamicModule = dynamicAssembly.DefineDynamicModule("MyAsm");

    // Now let's build a new type
    TypeBuilder dynamicAnonymousType = dynamicModule.DefineType("MyAnon", TypeAttributes.Public);

    // Let's add some fields to the type.
    FieldInfo dynamicFieldA = dynamicAnonymousType.DefineField("FieldA", typeof(TFieldTypeA), FieldAttributes.Public);
    FieldInfo dynamicFieldB = dynamicAnonymousType.DefineField("FieldB", typeof(TFieldTypeB), FieldAttributes.Public);

    // Return the type to the caller
    return dynamicAnonymousType.CreateType();
}

如您所见,这有点复杂。如果您想进一步研究此主题,请务必参考Reflection.Emit


1
谢谢,这是个不错的技巧,但不幸的是,它并不能解决我的主要问题,即在运行时我不知道匿名类型的样子(字段数量是可变的)。因此,我实际上无法在任何地方创建表达式。 - EPLKleijntjens
哦,我明白了。那么,这个问题是这样的,匿名类型是编译器的一个特性,也就是说,当你写一个匿名类型时,编译器实际上会将其构建为一个具有某些随机名称(在我的示例中为 '<>f__AnonymousType0'2')的特定类。因此,在运行时很难轻松地创建匿名类型。你可以使用 'Expression.New',但这需要一个构造函数,这意味着它需要是一个现有的类型。你可以定义一个类型来解决这个问题,但这实际上是一个完全不同的问题。如果想要,我可以写一个例子。 - sircodesalot
再次感谢您的帮助。我知道如何使用反射创建所需的类型,但问题在于,使用该类型,我无法创建创建新匿名类型的表达式。我在原来的问题中添加了一些上下文。如果我使用通过反射新创建的类型创建的表达式调用所提到的方法,我会收到一个异常,告诉我表达式的类型不匹配,因为它期望是属性表达式或带有匿名类型的表达式... - EPLKleijntjens
我在这部分遇到了麻烦:“我无法创建一个表达式来创建一个新的匿名类型”。匿名类型是编译器特性,仅在编译时可用。您无法在运行时创建“匿名类型”。您可以自己创建一个新类型,但严格来说那不是“匿名类型”。 - sircodesalot
2
我的意思是我可以创建这个表达式:**x=>new MyAnonymousType() {a=x.a, b=x.b}**,但我无法创建 **x=>new {a=x.a, b=x.b}**。我试图调用的方法期望第二种表达式(没有显式类型)。 - EPLKleijntjens

3
匿名类型是编译器的一个特性。如果您在编译时没有让编译器创建它们,那么您将不得不使用元编程 - 使用TypeBuilder或者CSharpCodeProvider。您最好使用元组 - 至少它们很容易创建(您可以轻松地使用Tuple.Create)。
至于表达式;我建议将其输入为Expression<Func<sometype, object>> - 这适用于任何公式。检查Expression的代码当然可以看到实际类型。

我有一个匿名类型作为参数表达式类型,我需要返回一个类型化的选择器。我尝试使用Expression<Func<dynamic, object>>返回类型,并将Expression.Lambda(Expression.Convert(body, typeof(object)), parameterExpression)强制转换为它,但它抱怨无法转换类型... 我该怎么办? - Alexander
@Alexander,你为什么需要做任何事情?如果你想转换为object:只需这样做即可。object x = theDynamicThing; - Marc Gravell
抱歉,让你有些困惑了。这是因为我不会创建一个新类型作为主题发起者,而是需要返回Lambda表达式:public static Expression<Func<dynamic, object>> GetPropertySelector(string propertyName, dynamic type) { var expr = Expression.Parameter(type, "x"); Expression body = expr; foreach (var member in propertyName.Split('.')) body = Expression.Property(body, member); return Expression.Lambda(Expression.Convert(body, typeof(object)), expr); },现在出现了奇怪的错误Cannot implicitly convert type ... to 'System.Linq.Expressions.Expression<System.Func<object,object>>' - Alexander
@Alexander l; 我不会猜测你的设置;这种问题确实需要从一个可重现、可运行的例子开始。 - Marc Gravell
我创建了一个测试示例:https://rextester.com/BNW81430(我知道看起来很奇怪,其中创建了匿名类型变量:))- 我正在尝试创建一个消耗两个“GroupJoin”的扩展方法。给定的方法应该是其中的一部分,用于构建第二个GroupJoin的选择器,其中包含匿名类型部分... - Alexander

-8

你可以简单地这样做

context.Students.Join(context.Courses, a => a.Course_id, b => b.Course_id, (a, b) => new { Student= a, Course= b }).Where(x => x.Student_id == studentId)
      .Select(y => new
      {
         StudentId = y.Student.StudentId,
         RegistrationNumber = y.Student.RegNo,
         Name = y.Student.Name,
         Coursename = y.Course.Name
      }).ToList();

2
这个回答如何解决问题? - svick
您可以在运行时获取匿名类型,而无需在已知类型中填充数据。这可能与问题不太相关,但很有帮助。 - Shahid Iqbal
1
重要的是要理解,在你的例子中,像 new 语句这样的操作并没有在编译时创建匿名类型。这就是为什么这个答案与问题无关,因此完全没有帮助。 - CodeFox

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接