如何合并Action与Action<T>重载函数以消除重复代码?
Great question! You're right that Action and Action<T> don't share a specific interface beyond Delegate, but there are a few ways to consolidate these two methods into a single generic implementation while avoiding duplicate code. Let's break down your options:
Option 1: Use Delegate with Pattern Matching (C# 7.0+)
You can accept a base Delegate parameter and use pattern matching to handle both Action and Action<T> cases in one method. This keeps all your logic in a single place:
private void CallVoidFunction<T>(Delegate action, object property = null) { // Your shared pre-invocation code here Console.WriteLine("Pre-invocation common code"); switch (action) { case Action noParamAction: noParamAction.Invoke(); break; case Action<T> paramAction when property != null: paramAction.Invoke((T)property); break; default: throw new ArgumentException("Unsupported action type", nameof(action)); } // Your shared post-invocation code here Console.WriteLine("Post-invocation common code"); }
Usage:
// Call with Action CallVoidFunction<object>(() => Console.WriteLine("No param action")); // Call with Action<int> CallVoidFunction<int>((num) => Console.WriteLine($"Param action: {num}"), 42);
Note: You need to specify the generic type parameter when calling (even object for the no-param case), but this consolidates your duplicate logic into one method.
Option 2: Wrap Action as Action<T> (Simpler Invocation)
If you want to avoid specifying the generic type for no-param actions, you can create a wrapper that converts an Action to an Action<T> (ignoring the parameter), then use a single generic method:
private void CallVoidFunction<T>(Action<T> action, object property = null) { // Shared pre-invocation code Console.WriteLine("Pre-invocation common code"); if (typeof(T) == typeof(EmptyParam) && property == null) { // Unwrap the no-param action ((Action)(Delegate)action).Invoke(); } else { action.Invoke((T)property); } // Shared post-invocation code Console.WriteLine("Post-invocation common code"); } // Helper marker type for no-param cases private class EmptyParam { } // Extension to wrap Action as Action<EmptyParam> private static Action<EmptyParam> WrapNoParamAction(Action action) { return _ => action.Invoke(); }
Usage:
// Call with Action (using wrapper) CallVoidFunction(WrapNoParamAction(() => Console.WriteLine("No param action"))); // Call with Action<int> CallVoidFunction((num) => Console.WriteLine($"Param action: {num}"), 42);
This makes invocation cleaner for both cases, though it requires a small helper type and wrapper method.
Comparing to Your ActionContainer Approach
Your current ActionContainer works, but it's a bit verbose since you have to instantiate a container object every time. The options above keep the logic in a single method without needing extra object instantiation.
A Compromise: Extract Common Code (Still Clean!)
While you mentioned you know you can extract common code, it's worth highlighting that this is often the most idiomatic approach in C#—it keeps your method signatures clean and leverages overloads (which are designed for exactly this scenario):
private void CallVoidFunction(Action action, object property = null) { RunCommonPreCode(property); action.Invoke(); RunCommonPostCode(property); } private void CallVoidFunction<T>(Action<T> action, object property = null) { RunCommonPreCode(property); action.Invoke((T)property); RunCommonPostCode(property); } // Shared code in private methods private void RunCommonPreCode(object property) { // Your duplicate pre-invocation logic here } private void RunCommonPostCode(object property) { // Your duplicate post-invocation logic here }
This maintains the type safety and clean invocation of your original overloads while eliminating duplicate code entirely. It's often the preferred approach because it's straightforward and aligns with C# best practices.
内容的提问来源于stack exchange,提问作者Alex

