有没有办法通过 Expression.New 实例化 class,其中 'ConstructorInfo' 由 ExpressionParameter 提供?

Is there a way to instantiate a class via Expression.New where 'ConstructorInfo' is supplied by an ExpressionParameter?

我想在 C# (ClassToBeInstantiated) 中实例化一个具有 public non- 的(非通用)class Expression.Block.

中通过 LINQ Expression.New 的无参数构造函数

根据@sweeper

的回答,请参阅下面的更新

描述

根据文档,Expression.New 接受参数的唯一重载需要一个 ConstructorInfo 参数。因为我事先无法访问该信息,但必须在 Expression.Block 中检索它。 所以我可以在传递到块中的 type ClassToBeInstantiated 上使用 Expression.Call

但是,如果我想将参数传递给构造函数,所有 Expression.New 重载仅接受 ConstructorInfo 作为参数或实例化。 Type 仅适用于使用 无参数 构造函数。

我也不能用 ParameterExpression 拿着 ConstructorInfo

问题

所以我的问题是:Expression.New 有解决这个问题的方法吗?我知道我可以通过另一个 Expression.Call 使用 ConstructorInfo.Invoke。但这对我来说似乎很尴尬,因为 - 至少在我看来 - Expression.New API 应该为我做这件事。

我是不是漏掉了什么?

感谢您的帮助、评论和回复。

待实例化的class

这里有一些额外的信息来进一步说明这个案例:

public class ClassToBeInstantiated
{
  public ClassToBeInstantiated(int count) { }
}

帮助程序 class 检索 ConstructorInfo

public static class GetConstructor
{
  public static ConstructorInfo GetNonGenericConstructorInfo(Type type, params Type[] typeArguments) =>
    type.GetConstructor(typeArguments);
}
[Test]
public void InstantiateTypeViaExpressionNew()
{
  var typeExpression = Expression.Parameter(typeof(Type), "type");
  var countExpression = Expression.Parameter(typeof(int), "count");
  var ctorExpression = Expression.Variable(typeof(ConstructorInfo), "constructorInfo");
  var block = Expression.Block
  (
    new[] { ctorExpression },
    Expression.Assign(ctorExpression, Expression.Call(typeof(GetConstructor), nameof(GetConstructor.GetNonGenericConstructorInfo),
      Type.EmptyTypes, typeExpression, Expression.Constant(new[] { countExpression.Type }))),
    Expression.New(/* error - obviously does not compile: ctorInfo */, countExpression)
  );

  var lambda = Expression.Lambda<Func<Type, int, object>>(block, typeExpression, countExpression);
  var func = lambda.Compile();
  var o = func.Invoke(typeof(ClassToBeInstantiated), 4);
  var instance = o as ClassToBeInstantiated;
  Assert.NotNull(instance);
}

更新为 Expression.Call 而不是 Expression.New

我根据@Sweeper 的回答更新了我原来问题的代码示例,以提供完整示例(以防有人感兴趣):

已更新助手class

在这里,我添加了字段 constructorInfoInvokeMethodInfo 用于检索 ConstructorInfo.Invoke() 方法的 MethodInfo(从 Expression.Block 内部调用:

public static class GetConstructor
{
  public static ConstructorInfo GetNonGenericConstructorInfo(Type type, params Type[] typeArguments) =>
    type.GetConstructor(typeArguments);

  public static readonly MethodInfo constructorInfoInvokeMethodInfo =
    typeof(ConstructorInfo).GetMethod(nameof(ConstructorInfo.Invoke), new[] { typeof(object[]) });
}

更新了 Expression.Block

的测试

在这里,我用 Expression.Call 替换了(非工作)Expression.New 以通过 ConstructorInfo.Invoke():

实例化类型
[Test]
public void InstantiateTypeViaExpressionCall()
{
  var typeExpression = Expression.Parameter(typeof(Type), "type");
  var countExpression = Expression.Parameter(typeof(int), "count");
  var ctorExpression = Expression.Variable(typeof(ConstructorInfo), "constructorInfo");
var block = Expression.Block
(
  new[] { ctorExpression },
  Expression.Assign
  (
    ctorExpression,
    Expression.Call(typeof(Activator), nameof(GetNonGenericConstructorInfo), Type.EmptyTypes, typeExpression, Expression.Constant(new[] { countExpression.Type }))
  ),
  Expression.Call(ctorExpression, constructorInfoInvokeMethodInfo, Expression.NewArrayInit(typeof(object), Expression.Convert(countExpression, typeof(object))))
);

  var lambda = Expression.Lambda<Func<Type, int, object>>(block, typeExpression, countExpression);
  var func = lambda.Compile();
  var o = func.Invoke(typeof(ClassToBeInstantiated), 4);
  var instance = o as ClassToBeInstantiated;
  Assert.NotNull(instance);
}

So my question: is there a way around this with Expression.New? I know I can use ConstructorInfo.Invoke via another Expression.Call.

这正是你应该做的。

考虑一下您要创建的表达式树。由于您想要实现的最终用法如下所示:

func.Invoke(typeof(ClassToBeInstantiated), 4);

func的表情一定是这样的:

(type, count) => GetConstructor.GetNonGenericConstructorInfo(type, typeof(int)).Invoke(new object[] {count})

这似乎也是您想要做的,只是使用了一个额外的局部变量。

不能是:

(type, count) => new type(count)

因为 new type(count) 不是有效的表达式。 type只是一个参数。

这与 Expression.New 创建的 new 表达式无关。您想要的表达式中根本没有 NewExpression。如果 Expression.New 突然开始在表达式树中插入 ConstructorInfo.Invoke 调用,那将是 非常奇怪 ,因为那是 而不是 什么它应该完全创建。

可以在这里使用Expression.New来创建如下表达式:

(type, count) => new ClassToBeInstantiated(count)

但我认为这不是您想要的...您希望 type 参数确定要实例化的类型,并假设单参数 int 构造函数可用,对吧?

使用sharplab.io,您可以编写您想要的lambda表达式,并查看编译器实际生成了哪些代码来构建表达式树。清理生成 for

的代码后
Expression<Func<Type, int, object>> func =
        (type, count) => GetConstructor.GetNonGenericConstructorInfo(type, typeof(int)).Invoke(new object[] {count});

你得到:

MethodInfo getConstructorMethod = typeof(GetConstructor).GetMethod(nameof(GetConstructor.GetNonGenericConstructorInfo));
MethodInfo invokeMethod = typeof(ConstructorInfo).GetMethod(nameof(ConstructorInfo.Invoke), new Type[] { typeof(object[]) });

ParameterExpression typeParam = Expression.Parameter(typeof(Type), "type");
ParameterExpression countParam = Expression.Parameter(typeof(int), "count");

// GetNonGenericConstructorInfo(type, typeof(int))
MethodCallExpression instance = Expression.Call(null, getConstructorMethod,
    typeParam, Expression.NewArrayInit(typeof(Type), Expression.Constant(typeof(int), typeof(Type)))
);
// // .Invoke(new object[] { count })
MethodCallExpression body = Expression.Call(instance, invokeMethod,
    Expression.NewArrayInit(typeof(object), Expression.Convert(countParam, typeof(object)))
);
Expression<Func<Type, int, object>> func = Expression.Lambda<Func<Type, int, object>>(body, typeParam, countParam);

并且 func.Compile()(typeof(ClassToBeInstantiated), 4) as ClassToBeInstantiated 不为空。