动态委托到最小 api
Dynamic delegate to minimal api
各位程序员大家好。
基本上,我想将动态构建的委托传递给最小的 api MapGet 或 MapPost 方法。这是构造委托的方法:
private static Delegate GetDelegate(Type type, MethodInfo method, ParameterInfo[] parameters)
{
/* Method dynamically build this lambda expression:
* (Type1 arg1, Type2 arg2, ..., TypeN argN) =>
{
var instance = GetTypeInstance(type);
return instance.SomeMethod(arg1, arg2, ..., argN);
}
* Where N = number of arguments
*/
var paramExpresions = new List<ParameterExpression>();
foreach (var parameter in parameters)
paramExpresions.Add(Expression.Parameter(parameter.ParameterType, parameter.Name));
// Instance variable
var instance = Expression.Variable(type, "instance");
// Get instance of type
MethodInfo getTypeInstance = typeof(DynamicControllerCompiler).GetMethod("GetTypeInstance");
var callExpression = Expression.Call(getTypeInstance, Expression.Constant(type));
var expressionConversion = Expression.Convert(callExpression, type);
var assignSentence = Expression.Assign(instance, expressionConversion);
var returnTarget = Expression.Label(method.ReturnType);
var returnExpression = Expression.Return(returnTarget, Expression.Call(instance, method, paramExpresions), method.ReturnType);
var returnLabel = Expression.Label(returnTarget, Expression.Default(method.ReturnType));
var fullBlock = Expression.Block(
new[] { instance },
assignSentence,
returnExpression,
returnLabel
);
var lambda = Expression.Lambda(fullBlock, "testLambda", paramExpresions);
return lambda.Compile();
}
引用的方法“GetTypeInstance”只是 returns 来自容器的服务,但为了简单起见,让它只是:
public static object GetTypeInstance(Type type)
{
return new EchoService();
}
服务很简单:
public class EchoService
{
public string Echo(string message)
{
return message;
}
public string EchoDouble(string message)
{
return message + "_" + message;
}
}
所以我想将一个 get 方法映射到最小的 api,像这样使用它:
var type = typeof(EchoService);
foreach (var method in type.GetMethods())
{
ParameterInfo[] parameters = method.GetParameters();
var methodDelegate = GetDelegate(type, method, parameters);
//test
var result = methodDelegate.DynamicInvoke("test");
app.MapGet($"api/{method.Name}", methodDelegate);
}
为了测试动态委托是否有效,我用“DynamicInvoke”调用它,一切似乎都很好。但是,如果我将委托传递给 MapGet,则会抛出错误:
System.InvalidOperationException: 'A parameter does not have a name! Was it generated? All parameters must be named.'
我似乎无法理解发生了什么。如果由 DynamicInvoke 调用,委托工作正常,并且所有参数内部都有名称。
问题的根源在于表达式树编译的工作方式。我没有足够的知识来解释原因,但默认情况下它不会发出参数名称:
Expression<Func<int, string>> expr = i => i.ToString();
var compiledMethod = expr.Compile().Method;
Console.WriteLine(compiledMethod.GetParameters().Count(p => p.Name != null)); // prints "0"
你可以尝试通过使用 Compile
重载接受 bool preferInterpretation
参数来克服这个问题(至少它适用于我的设置)但是你将面临另一个问题 - 编译方法实际上有两个参数(一个用于处理闭包,我假设):
compiledMethod = expr.Compile(true).Method;
Console.WriteLine(compiledMethod.GetParameters().Count(p => p.Name != null)); // prints "2"
而且我从这里看不到简单的解决方法。
您可以尝试的其他方法 - 使用 System.Reflection.Emit
namespace or Roslyn but I would say that better one would be to generate this code during build time using source generators 的动态代码生成。
各位程序员大家好。 基本上,我想将动态构建的委托传递给最小的 api MapGet 或 MapPost 方法。这是构造委托的方法:
private static Delegate GetDelegate(Type type, MethodInfo method, ParameterInfo[] parameters)
{
/* Method dynamically build this lambda expression:
* (Type1 arg1, Type2 arg2, ..., TypeN argN) =>
{
var instance = GetTypeInstance(type);
return instance.SomeMethod(arg1, arg2, ..., argN);
}
* Where N = number of arguments
*/
var paramExpresions = new List<ParameterExpression>();
foreach (var parameter in parameters)
paramExpresions.Add(Expression.Parameter(parameter.ParameterType, parameter.Name));
// Instance variable
var instance = Expression.Variable(type, "instance");
// Get instance of type
MethodInfo getTypeInstance = typeof(DynamicControllerCompiler).GetMethod("GetTypeInstance");
var callExpression = Expression.Call(getTypeInstance, Expression.Constant(type));
var expressionConversion = Expression.Convert(callExpression, type);
var assignSentence = Expression.Assign(instance, expressionConversion);
var returnTarget = Expression.Label(method.ReturnType);
var returnExpression = Expression.Return(returnTarget, Expression.Call(instance, method, paramExpresions), method.ReturnType);
var returnLabel = Expression.Label(returnTarget, Expression.Default(method.ReturnType));
var fullBlock = Expression.Block(
new[] { instance },
assignSentence,
returnExpression,
returnLabel
);
var lambda = Expression.Lambda(fullBlock, "testLambda", paramExpresions);
return lambda.Compile();
}
引用的方法“GetTypeInstance”只是 returns 来自容器的服务,但为了简单起见,让它只是:
public static object GetTypeInstance(Type type)
{
return new EchoService();
}
服务很简单:
public class EchoService
{
public string Echo(string message)
{
return message;
}
public string EchoDouble(string message)
{
return message + "_" + message;
}
}
所以我想将一个 get 方法映射到最小的 api,像这样使用它:
var type = typeof(EchoService);
foreach (var method in type.GetMethods())
{
ParameterInfo[] parameters = method.GetParameters();
var methodDelegate = GetDelegate(type, method, parameters);
//test
var result = methodDelegate.DynamicInvoke("test");
app.MapGet($"api/{method.Name}", methodDelegate);
}
为了测试动态委托是否有效,我用“DynamicInvoke”调用它,一切似乎都很好。但是,如果我将委托传递给 MapGet,则会抛出错误:
System.InvalidOperationException: 'A parameter does not have a name! Was it generated? All parameters must be named.'
我似乎无法理解发生了什么。如果由 DynamicInvoke 调用,委托工作正常,并且所有参数内部都有名称。
问题的根源在于表达式树编译的工作方式。我没有足够的知识来解释原因,但默认情况下它不会发出参数名称:
Expression<Func<int, string>> expr = i => i.ToString();
var compiledMethod = expr.Compile().Method;
Console.WriteLine(compiledMethod.GetParameters().Count(p => p.Name != null)); // prints "0"
你可以尝试通过使用 Compile
重载接受 bool preferInterpretation
参数来克服这个问题(至少它适用于我的设置)但是你将面临另一个问题 - 编译方法实际上有两个参数(一个用于处理闭包,我假设):
compiledMethod = expr.Compile(true).Method;
Console.WriteLine(compiledMethod.GetParameters().Count(p => p.Name != null)); // prints "2"
而且我从这里看不到简单的解决方法。
您可以尝试的其他方法 - 使用 System.Reflection.Emit
namespace or Roslyn but I would say that better one would be to generate this code during build time using source generators 的动态代码生成。