如何实现两个独立Partial Class同名方法协同输出且无依赖?
Great question! The requirement to dynamically combine results from multiple partial classes (and gracefully handle their removal) is a common scenario, and there are a few clean approaches beyond the event + dependency injection combo you already have in mind. Let's break them down with code examples and pros/cons.
1. Interface + Dependency Injection (Explicit Implementation Registration)
This is a more structured take on DI that leans on abstraction instead of events. The idea is to define a common interface for your DoFoo logic, have each partial class implement it, then inject all implementations into your parent class.
Example Code:
First, define the interface:
public interface IFooHandler { string DoFoo(); }
Then your partial classes:
public partial class FooHandlerA : IFooHandler { public string DoFoo() => "1234"; } public partial class FooHandlerB : IFooHandler { public string DoFoo() => "5678"; }
Your parent class injects all instances of IFooHandler and combines results:
public class ParentClass { private readonly IEnumerable<IFooHandler> _fooHandlers; public ParentClass(IEnumerable<IFooHandler> fooHandlers) { _fooHandlers = fooHandlers; } public string CombineFooResults() { return string.Concat(_fooHandlers.Select(h => h.DoFoo())); } }
How it fits your requirement:
- When you register both
FooHandlerAandFooHandlerBin your DI container,CombineFooResultsreturns"12345678". - Remove one handler from your DI registration (or delete the class entirely) and the method will only return the remaining handler's result.
Pros & Cons:
- ✅ Follows SOLID principles (dependency inversion, single responsibility)
- ✅ Easy to test (mock
IFooHandlerinstances) - ❌ Requires a DI container (though you could manually build the list if needed)
2. Static Delegate Registration (No DI Required)
If you want a lighter-weight approach without DI, you can use static delegates to let each partial class register its DoFoo method on startup.
Example Code:
First, create a static registry in your parent class (or a separate helper):
public static class FooHandlerRegistry { public static List<Func<string>> RegisteredHandlers { get; } = new List<Func<string>>(); }
Then each partial class registers itself in its static constructor:
public partial class FooHandlerA { static FooHandlerA() { FooHandlerRegistry.RegisteredHandlers.Add(DoFoo); } public static string DoFoo() => "1234"; } public partial class FooHandlerB { static FooHandlerB() { FooHandlerRegistry.RegisteredHandlers.Add(DoFoo); } public static string DoFoo() => "5678"; }
Your parent class uses the registry to combine results:
public class ParentClass { public string CombineFooResults() { return string.Concat(FooHandlerRegistry.RegisteredHandlers.Select(handler => handler())); } }
How it fits your requirement:
- When both classes exist, their static constructors run on first access, adding their methods to the registry.
- Delete one class, and its registration never happens—so only the remaining handler's result is used.
Pros & Cons:
- ✅ No DI container needed
- ✅ Very lightweight
- ❌ Static state can be tricky to test (you may need to clear the registry between tests)
- ❌ Thread safety: if handlers are registered concurrently, you'll need to use a thread-safe collection like
ConcurrentBag<Func<string>>
3. Reflection-Based Scanning (Dynamic Discovery)
Another option is to use reflection to automatically find all classes that implement your DoFoo logic (you can use a custom attribute to mark them for easy discovery).
Example Code:
First, create a custom attribute to mark your handlers:
[AttributeUsage(AttributeTargets.Class)] public class FooHandlerAttribute : Attribute { }
Mark your partial classes with the attribute:
[FooHandler] public partial class FooHandlerA { public string DoFoo() => "1234"; } [FooHandler] public partial class FooHandlerB { public string DoFoo() => "5678"; }
Your parent class scans the assembly for marked classes, instantiates them, and calls DoFoo:
public class ParentClass { public string CombineFooResults() { var result = new StringBuilder(); var assembly = Assembly.GetExecutingAssembly(); // Find all classes with FooHandlerAttribute var handlerTypes = assembly.GetTypes() .Where(t => t.GetCustomAttribute<FooHandlerAttribute>() != null); foreach (var type in handlerTypes) { var handler = Activator.CreateInstance(type); var doFooMethod = type.GetMethod("DoFoo"); if (doFooMethod != null) { var fooResult = doFooMethod.Invoke(handler, null) as string; result.Append(fooResult); } } return result.ToString(); } }
How it fits your requirement:
- Reflection finds all marked classes at runtime—if you delete one, it won't show up in the scan.
- No manual registration needed; just mark classes with the attribute.
Pros & Cons:
- ✅ No DI or manual registration
- ✅ Fully dynamic (add/remove classes without changing parent code)
- ❌ Reflection has a small performance overhead (negligible for most scenarios, but worth noting)
- ❌ Tighter coupling (relies on method names/attributes instead of interfaces)
Which Approach Should You Choose?
- If you're already using a DI container (like ASP.NET Core's built-in one), go with the Interface + DI approach—it's the most maintainable and testable.
- If you want something lightweight without DI, the Static Delegate Registration is a solid choice (just watch thread safety).
- If you need full dynamic discovery (no registration steps at all), Reflection-Based Scanning works, but be aware of the tradeoffs.
内容的提问来源于stack exchange,提问作者WynDiesel

