12

In C# I have following methods defined in given class (non static):

int MyMethod(ScriptEngine script, int a, int b) { return a + b; } void MyMethod2(ScriptEngine script, string c) { // do something with c } 

I want to create wrapping lambda / Action / Delegate / MethodInfo (all are accepted by script engine) that automatically passes ScriptEngine and this from given, predefined variables.

So far I've experimenting with:

// With overloads up to 16 template parameters Action<T1> Wrap<T1>(Action<ScriptEngine, T1> func, ScriptEngine script) { return (Action<T1>) ((t1) => func(script, t1)); } 

But when called on MyMethod2 I've got The type arguments for method … cannot be inferred from the usage. Try specifying the type arguments explicitly. If I specify the template arguments explicity, it works, but I want to avoid such specification.

Is there any other way (not neccesery following my solution) I can create such wrapper automatically (or semi-automatically)?

It is worth mentioning that there is a dedicated abstract method void RegisterAll(ScriptEngine script) that can register required members of given subclass.

Here is an example of what I am trying to achieve:

class ScriptEngine { // Stub to have complete example, actual implementation is defined elsewhere void RegisterApi(string name, MethodInfo methodInfo) { } void RegisterApi(string name, Delegate delegateSelf) { } } class Api { int MyMethod(ScriptEngine script, int a, int b) { return a + b; } void MyMethod2(ScriptEngine script, string c) { // do something with c } void RegisterAll(ScriptEngine script) { // Is there any way to shorten this up (not providing MyMethod twice, not providing template arguments?) script.RegisterApi(nameof(MyMethod), (Delegate)Wrap<string>(MyMethod, script)); } } 

The problem is how to improve this RegisterApi method so it:

  • Takes the method only once
  • Does not require specifying arguments via template method
1
  • Comments are not for extended discussion; this conversation has been moved to chat. Commented Oct 16, 2020 at 13:33

2 Answers 2

2

There is actually another solution that does not involve emiting new Expressions (could fail on iOS!)

First, let us define following wrapper:

 private class Wrapper { public readonly object container; public readonly MethodInfo method; public readonly ScriptEngine engine; public Wrapper(object container, MethodInfo method, ScriptEngine engine) { this.container = container; this.method = method; this.engine = engine; } public Action CreateAction() { return () => method.Invoke(container, new object[] { engine }); } public Action<T1> CreateAction<T1>() { return (arg1) => method.Invoke(container, new object[] { engine, arg1 }); } // etc } 

Now you can register method like that:

 var type = typeof(Wrapper); var instance = Activator.CreateInstance(type, new object[] { container, methodInfo, engine }); MethodInfo methodActual = null; if (methodInfo.ReturnType == typeof(void)) { var methods = type.GetMethods().Where(x => x.Name == "CreateAction"); foreach (var method in methods) { if (method.GetGenericArguments().Length == methodInfo.GetParameters().Length - 1) { methodActual = method.MakeGenericMethod(methodInfo.GetParameters().Skip(1).Select(x => x.ParameterType).ToArray()); } } } var actionToRegister = methodActual.Invoke(instance, new object[0]); 
Sign up to request clarification or add additional context in comments.

Comments

1

The solution is to use Expression Tree to create a simple delegate wrapper.

 var parameterEngine = Expression.Constant(script, typeof(ScriptEngine)); var parameterCaller = Expression.Constant(this, this.GetType()); // Build parameters, skipping the first. Note that trailing "ToList" is important! var parameters = methodInfo.GetParameters().Skip(1).Select(x => Expression.Parameter(x.ParameterType, x.Name)).ToList(); // Inject parameter var mergedParameters = new List<Expression>(); mergedParameters.Add(parameterEngine); mergedParameters.AddRange(parameters); // Bind together var call = Expression.Call(parameterCaller, methodInfo, mergedParameters); var expression = Expression.Lambda(call, parameters); var method = expression.Compile(); // Method can now be registered 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.