Warm tip: This article is reproduced from serverfault.com, please click

c#-创建调用泛型方法的可执行表达式

(c# - Create executable expression calling generic method)

发布于 2021-10-14 12:04:16

好的,假设我有这种类层次结构:

/// In 3rd party library 
public class WidgetBase
{
    protected void Register<THandler>(Action<THandler> handler) { /* do something */ }
}

public record Message1();
public record Message2();

public sealed class MyWidget : Base
{
    public MyClass()
    {      
        RegisterHandlers(this);
    }

    [Handler]
    private void Handle(Message1 msg) {}
    
    [Handler]
    private void Handle(Message2 msg) {}
}

public static class Ext
{
    // Would prefer extension or normal static method
    // and not impose inheritance by putting this
    // in an intermediatery base class.
    public static void RegisterHandlers<T>(this T t)
    {
        // Discovers methods with 'Handler' attribute and calls t.Register()
    }
}

因此,目标是实现RegisterHandlers对对象的方法进行自省,然后生成一个Expression调用基类注册方法的可执行文件。想想 Asp.Net Core Controller 处理程序的行。

我只是不知道该怎么做。尽管即使是纯反射解决方案也可以,但表达式的重点是提高性能。

我可以发现方法,甚至生成一个表达式,t => this.Handle(t)但无法理解如何在没有类型的情况下完成对泛型基类方法的调用。

SO中有很多类似的问题,但找不到确切的解决方案。

[编辑] 使示例更加清晰。

Questioner
MarkkuL
Viewed
0
canton7 2021-10-14 21:19:38

可以执行以下操作:

public static void RegisterHandlers<T>(T t) where T : Base
{
    var methods = typeof(T).GetMethods(BindingFlags.NonPublic | BindingFlags.Instance).Where(x => x.GetCustomAttribute<HandlerAttribute>() != null);
    var registerMethod = typeof(Base).GetMethod("Register", BindingFlags.NonPublic | BindingFlags.Instance);
    
    var blockItems = new List<Expression>();
    foreach (var method in methods)
    {
        if (method.IsGenericMethod || method.GetParameters().Length != 1 || method.ReturnType != typeof(void))
            throw new Exception($"Invalid method signature for method {method}");
        
        // The type of the Handle method's parameter (e.g. SomeType1 or SomeType2)
        var parameterType = method.GetParameters()[0].ParameterType;
        
        // MethodInfo for e.g. the Register<SomeType1> method
        var typedRegisterMethod = registerMethod.MakeGenericMethod(parameterType);
        
        // The type of delegate we'll pass to Register, e.g. Action<SomeType1>
        var delegateType = typeof(Action<>).MakeGenericType(parameterType);
        
        // Construct the x => Handle(x) delegate
        var delegateParameter = Expression.Parameter(parameterType);
        var delegateConstruction = Expression.Lambda(delegateType, Expression.Call(Expression.Constant(t), method, delegateParameter), delegateParameter);
        
        // Construct the Register(delegate) call
        var methodCall = Expression.Call(Expression.Constant(t), typedRegisterMethod, new[] { delegateConstruction });
        
        // Add this to the list of expressions we'll put in our block
        blockItems.Add(methodCall);
    }
    
    var compiled = Expression.Lambda<Action>(Expression.Block(blockItems)).Compile();
    compiled();
}

在 dotnetfiddle.net 上查看

请注意,与仅使用反射相比,这样做没有特别的优势。你没有缓存生成的compiledor blockItems,这是为此类事情使用编译表达式的节省的来源。


但是,你可以稍微扩展它,并添加这样的缓存:

private static class Cache<T> where T : Base
{
    public static readonly Action<T> Instance = CreateInstance();
    
    private static Action<T> CreateInstance()
    {
        var methods = typeof(T).GetMethods(BindingFlags.NonPublic | BindingFlags.Instance).Where(x => x.GetCustomAttribute<HandlerAttribute>() != null);
        var registerMethod = typeof(Base).GetMethod("Register", BindingFlags.NonPublic | BindingFlags.Instance);

        var instanceParameter = Expression.Parameter(typeof(T));

        var blockItems = new List<Expression>();
        foreach (var method in methods)
        {
            if (method.IsGenericMethod || method.GetParameters().Length != 1 || method.ReturnType != typeof(void))
                throw new Exception($"Invalid method signature for method {method}");

            // The type of the Handle method's parameter (e.g. SomeType1 or SomeType2)
            var parameterType = method.GetParameters()[0].ParameterType;

            // MethodInfo for e.g. the Register<SomeType1> method
            var typedRegisterMethod = registerMethod.MakeGenericMethod(parameterType);

            // The type of delegate we'll pass to Register, e.g. Action<SomeType1>
            var delegateType = typeof(Action<>).MakeGenericType(parameterType);

            // Construct the x => Handle(x) delegate
            var delegateParameter = Expression.Parameter(parameterType);
            var delegateConstruction = Expression.Lambda(delegateType, Expression.Call(instanceParameter, method, delegateParameter), delegateParameter);

            // Construct the Register(delegate) call
            var methodCall = Expression.Call(instanceParameter, typedRegisterMethod, new[] { delegateConstruction });

            // Add this to the list of expressions we'll put in our block
            blockItems.Add(methodCall);
        }

        var compiled = Expression.Lambda<Action<T>>(Expression.Block(blockItems), instanceParameter).Compile();
        return compiled;
    }
}

public static void RegisterHandlers<T>(T t) where T : Base
{
    Cache<T>.Instance(t);
}

在 dotnetfiddle.net 上查看

请注意我们现在如何将T实例作为参数,这让我们可以缓存生成的Action<T>.