I would like to instantiate a (non-generic) class in C# (ClassToBeInstantiated
) that has a public non-parameterless constructor via LINQ Expression.New
inside an Expression.Block
.
see update below, based on answer from @sweeper
Description
According to the documentation, the only overloads for Expression.New
that do accept arguments require a ConstructorInfo
argument. As I do not have access to that info beforehand, but have to retrieve this inside the Expression.Block
.
So I am able to use an Expression.Call
on the type
ClassToBeInstantiated
that is passed into the block.
However, all Expression.New
overloads only accept either ConstructorInfo
as an argument or for an instantiation if I want to pass arguments to the constructor. Type
is only available for using a parameterless constructor.
I cannot use a ParameterExpression
holding a ConstructorInfo
either.
Question
So my question: is there a way around this with Expression.New
? I know I can use ConstructorInfo.Invoke
via another Expression.Call
. But this seems akward to me, as -at least in my opinion- the Expression.New
API should exactly do this for me.
Am I missing something?
Thanks for your help, comments and reply.
The class to be instantiated
Here is some additional information to further illustrate the case:
public class ClassToBeInstantiated
{
public ClassToBeInstantiated(int count) { }
}
Helper class to retrieve 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);
}
Update with Expression.Call
instead of Expression.New
I updated the code example from my original question, based on the answer from @Sweeper to provide a full example (in case someone is interested):
Updated helper class
Here, I added the field constructorInfoInvokeMethodInfo
for retrieval of the MethodInfo
for the ConstructorInfo.Invoke()
method (to be called from inside the 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[]) });
}
Updated test for Expression.Block
Here, I replaced the (non-working) Expression.New
with an Expression.Call
to instantiate the type via 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);
}
CodePudding user response:
So my question: is there a way around this with
Expression.New
? I know I can useConstructorInfo.Invoke
via anotherExpression.Call
.
This is exactly what you should do.
Think about the expression tree that you want to create. Since the final usage that you want to achieve looks like:
func.Invoke(typeof(ClassToBeInstantiated), 4);
func
's expression must look like:
(type, count) => GetConstructor.GetNonGenericConstructorInfo(type, typeof(int)).Invoke(new object[] {count})
This seems to be what you were trying to do too, just with an extra local variable.
It cannot be:
(type, count) => new type(count)
Because new type(count)
is not a valid expression. type
is just a parameter.
This has nothing to do with new
expressions that Expression.New
creates. There are no NewExpression
s in your desired expression at all. It would be very weird if Expression.New
suddenly starts inserting ConstructorInfo.Invoke
calls in the expression tree, because that is not what it is supposed to create at all.
You could use Expression.New
here to create expressions such as:
(type, count) => new ClassToBeInstantiated(count)
But I don't think that's what you want... You want the type
parameter to determine what type to instantiate, and assume that a one-parameter int
constructor to be available, right?
Using sharplab.io, you can write the lambda expression you want, and see what code the compiler actually generates for building the expression tree. After cleaning up the code that generates for
Expression<Func<Type, int, object>> func =
(type, count) => GetConstructor.GetNonGenericConstructorInfo(type, typeof(int)).Invoke(new object[] {count});
You get:
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);
And func.Compile()(typeof(ClassToBeInstantiated), 4) as ClassToBeInstantiated
is not null.