C#重构:实现通用Wrapper的Invoke方法,替代多WCF包装类
Hey there! Let's tackle your problem of cutting down on those repetitive WCF wrapper classes while implementing that clean wrapper.Invoke(x => x.GetOrderById(5)) pattern you're aiming for. I've worked through similar scenarios with WCF and dual-mode services, so here's a practical implementation plus some optimizations to make this even better.
First: Implement the Generic Wrapper's Invoke Method
Your existing ServiceFactory<T> is already the foundation here—it handles serving up either the local offline implementation or the WCF proxy based on your mode. The generic wrapper will use this factory to get the service instance, handle the method call via lambda, and clean up resources properly (critical for WCF!).
Step 1: Build the Generic ServiceWrapper Class
This class will work with any service interface (like IOrderService) and handle both return-value and void methods:
public class ServiceWrapper<T> where T : class { private readonly ServiceFactory<T> _serviceFactory; public ServiceWrapper(ServiceFactory<T> serviceFactory) { _serviceFactory = serviceFactory; } // For methods that return a value public TResult Invoke<TResult>(Func<T, TResult> serviceCall) { var service = _serviceFactory.GetService(); try { // Execute the lambda directly against the service instance return serviceCall(service); } finally { // Clean up resources based on service type CleanUpServiceInstance(service); } } // Overload for void methods public void Invoke(Action<T> serviceCall) { var service = _serviceFactory.GetService(); try { serviceCall(service); } finally { CleanUpServiceInstance(service); } } // Reusable cleanup logic private void CleanUpServiceInstance(T service) { // Handle WCF proxies specifically (they implement ICommunicationObject) if (service is ICommunicationObject wcfChannel) { if (wcfChannel.State == CommunicationState.Faulted) { wcfChannel.Abort(); // Abort if channel is in a bad state } else { wcfChannel.Close(); // Properly close healthy channels } } // Dispose local implementations if they support it else if (service is IDisposable disposable) { disposable.Dispose(); } } }
Step 2: How This Works
- The
Invokemethods take a lambda (likex => x.GetOrderById(5)) which directly calls the target method on the service instance from your factory. - The
finallyblock ensures we always clean up resources: WCF channels need special handling (abort on fault, close otherwise), while local services just get disposed if they implementIDisposable. - You don't need any service-specific wrapper classes anymore—this single
ServiceWrapper<T>works for all 12 of your service interfaces.
Step 3: Usage Example
Here's how you'd call your IOrderService with this wrapper:
// Set up your factory and wrapper (you can inject these if using DI) var orderServiceFactory = new ServiceFactory<IOrderService>(); var orderWrapper = new ServiceWrapper<IOrderService>(orderServiceFactory); // Call a method with a return value var order = orderWrapper.Invoke(x => x.GetOrderById(5)); // Call a void method (like updating status) orderWrapper.Invoke(x => x.UpdateOrderStatus(5, OrderStatus.Shipped));
Next: Optimizations to Polish This Implementation
Now that the core Invoke method is working, let's make this more robust and efficient.
1. Extract WCF Channel Management to a Helper
WCF channel handling has some boilerplate—move that to a dedicated helper to keep your wrapper clean:
public static class WcfChannelHelper<T> where T : class { // Cache ChannelFactory to avoid expensive re-creation private static readonly Lazy<ChannelFactory<T>> _channelFactory = new Lazy<ChannelFactory<T>>(() => new ChannelFactory<T>("*")); public static TResult Execute<TResult>(Func<T, TResult> operation) { var channel = _channelFactory.Value.CreateChannel(); try { var result = operation(channel); ((ICommunicationObject)channel).Close(); return result; } catch { ((ICommunicationObject)channel).Abort(); throw; } } public static void Execute(Action<T> operation) { var channel = _channelFactory.Value.CreateChannel(); try { operation(channel); ((ICommunicationObject)channel).Close(); } catch { ((ICommunicationObject)channel).Abort(); throw; } } }
Then update your ServiceFactory<T> to use this helper for WCF instances—now your factory handles mode switching, and the helper takes care of WCF-specific logic.
2. Use Dependency Injection (DI) for Wrapper/Factory
If you're using a DI container (like Autofac, Microsoft.Extensions.DependencyInjection), register your generic types so you don't have to manually instantiate them everywhere:
For Microsoft DI:
services.AddSingleton(typeof(ServiceFactory<>)); services.AddTransient(typeof(ServiceWrapper<>));
Then inject ServiceWrapper<IOrderService> directly into your controllers/services—no more manual factory setup!
3. Unify Exception Handling
Wrap WCF-specific exceptions (like FaultException) into your own business exceptions so upper layers don't have to deal with WCF details:
public TResult Invoke<TResult>(Func<T, TResult> serviceCall) { var service = _serviceFactory.GetService(); try { return serviceCall(service); } catch (FaultException ex) { // Convert WCF fault to your custom business exception throw new ServiceInvocationException("Service call failed", ex); } finally { CleanUpServiceInstance(service); } }
4. Cache WCF Channel Factories
As you saw in the WcfChannelHelper, using Lazy<T> to cache the ChannelFactory<T> avoids the cost of creating a new factory every time—WCF factories are expensive to spin up, so this gives a nice performance boost.
Final Thoughts
This approach eliminates all those redundant service-specific WCF wrappers while keeping your code clean and maintainable:
- The generic
ServiceWrapper<T>handles all service invocation and resource cleanup ServiceFactory<T>manages switching between offline/online modes- The lambda-based
Invokesyntax keeps your service calls readable and close to direct interface calls
内容的提问来源于stack exchange,提问作者Glauco Cucchiar

