Home > other >  Is there a way to instantiate a class via Expression.New where 'ConstructorInfo' is suppli
Is there a way to instantiate a class via Expression.New where 'ConstructorInfo' is suppli

Time:11-28

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 use ConstructorInfo.Invoke via another Expression.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 NewExpressions 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.

  • Related