有没有办法通过 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
不为空。
我想在 C# (ClassToBeInstantiated
) 中实例化一个具有 public non- 的(非通用)class Expression.Block
.
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 useConstructorInfo.Invoke
via anotherExpression.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
不为空。