You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何更简洁实现支持任意Action的DoubleAction泛型结构?

如何简洁实现包含Before/After回调的DoubleAction结构体?

问题描述

我在实现回调功能时需要同时使用Before和After两个回调,尝试用泛型结构体封装,但遇到了问题:

public struct DoubleAction<T> where T : System.Delegate
{
    public DoubleAction()
    {
        Before = delegate { };
        After = delegate { };
    }

    public T Before;
    public T After;
}

// 使用示例:
DoubleAction<Action<int, string>> OnWrite;

由于Action、Action<T1>等类型没有共同的可实例化父类,泛型约束T : Delegate无法直接创建对应类型的空委托(我不想每次用?.调用,频繁使用会影响性能)。目前只能为每个Action类型编写对应的DoubleAction重载结构体:

public struct DoubleAction<T>
{
    public DoubleAction()
    {
        Before = delegate { };
        After = delegate { };
    }

    public Action<T> Before;
    public Action<T> After;
}

public struct DoubleAction<T1, T2> { ... }
public struct DoubleAction<T1, T2, T3> { ... }
// 依此类推

有没有更简洁的实现方式?


解决方案:单泛型结构体+静态工厂方法

只需要定义一个泛型结构体,通过静态工厂方法结合Delegate.CreateDelegate生成对应类型的空委托,无需编写多个重载结构体:

public readonly struct DoubleAction<T> where T : Delegate
{
    public T Before { get; }
    public T After { get; }

    private DoubleAction(T before, T after)
    {
        Before = before;
        After = after;
    }

    /// <summary>
    /// 创建带有空Before/After回调的实例
    /// </summary>
    public static DoubleAction<T> CreateEmpty()
    {
        var delegateType = typeof(T);
        // 获取委托Invoke方法的参数列表,匹配对应的空实现方法
        var invokeParams = delegateType.GetMethod("Invoke")!.GetParameters();
        var paramTypes = invokeParams.Select(p => p.ParameterType).ToArray();
        
        var emptyMethod = typeof(DoubleAction<T>)
            .GetMethod(nameof(EmptyAction), BindingFlags.NonPublic | BindingFlags.Static, paramTypes)!;
        
        var emptyDelegate = (T)Delegate.CreateDelegate(delegateType, emptyMethod);
        return new DoubleAction<T>(emptyDelegate, emptyDelegate);
    }

    // 定义对应Action参数数量的空实现方法,覆盖.NET Action支持的参数范围
    private static void EmptyAction() { }
    private static void EmptyAction<T1>(T1 arg1) { }
    private static void EmptyAction<T1, T2>(T1 arg1, T2 arg2) { }
    private static void EmptyAction<T1, T2, T3>(T1 arg1, T2 arg2, T3 arg3) { }
    private static void EmptyAction<T1, T2, T3, T4>(T1 arg1, T2 arg2, T3 arg3, T4 arg4) { }
    // 可继续扩展到Action支持的16个参数上限
}

使用方式

// 初始化带有空回调的实例,直接调用无需空检查
var OnWrite = DoubleAction<Action<int, string>>.CreateEmpty();
OnWrite.Before(123, "test");
OnWrite.After(123, "test");

原理说明

  1. 通过Delegate.CreateDelegate将匹配参数数量的EmptyAction方法转换为指定的委托类型T,彻底避免了空委托(null)的问题。
  2. 只需要维护一组EmptyAction重载(对应.NET Action的参数数量上限),即可支持所有Action类型,无需编写多个DoubleAction重载结构体。
  3. 使用readonly struct提升值类型的性能,避免不必要的内存拷贝。

内容的提问来源于stack exchange,提问作者CREAsTIVE

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 14:37:02