如何更简洁实现支持任意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");
原理说明
- 通过
Delegate.CreateDelegate将匹配参数数量的EmptyAction方法转换为指定的委托类型T,彻底避免了空委托(null)的问题。 - 只需要维护一组
EmptyAction重载(对应.NET Action的参数数量上限),即可支持所有Action类型,无需编写多个DoubleAction重载结构体。 - 使用
readonly struct提升值类型的性能,避免不必要的内存拷贝。
内容的提问来源于stack exchange,提问作者CREAsTIVE
相关产品推荐
相关产品推荐

