如何编程捕获接口类库所有错误?实现onError式通用异常捕获器
Great question—repeating try/catch blocks across every class in a library is tedious and error-prone. Let’s walk through how to build a clean, centralized exception handling system using an IExceptionWrapper that acts like a global onError handler, so you never have to write those repetitive blocks again.
IExceptionWrapper Contract First, create an interface that defines the core functionality: wrapping method execution and exposing an event for error notifications (your onError equivalent). This keeps your handling logic decoupled from the library’s business code.
public interface IExceptionWrapper { // Wrap synchronous operations that return a value TResult Execute<TResult>(Func<TResult> operation); // Wrap synchronous void operations void Execute(Action operation); // Wrap async operations that return a value Task<TResult> ExecuteAsync<TResult>(Func<Task<TResult>> operation); // Wrap async void operations Task ExecuteAsync(Func<Task> operation); // Event triggered whenever an exception is caught (your "onError" hook) event Action<Exception> OnExceptionCaught; }
Next, build a concrete implementation that handles exception catching, logging, and triggers the OnExceptionCaught event. This is where your centralized error logic lives.
public class GlobalExceptionWrapper : IExceptionWrapper { public event Action<Exception> OnExceptionCaught; public TResult Execute<TResult>(Func<TResult> operation) { try { return operation(); } catch (Exception ex) { HandleException(ex); throw; // Re-throw if you want callers to still handle the exception // Or return default(TResult) if you want to swallow it intentionally } } public void Execute(Action operation) { try { operation(); } catch (Exception ex) { HandleException(ex); throw; } } public async Task<TResult> ExecuteAsync<TResult>(Func<Task<TResult>> operation) { try { return await operation(); } catch (Exception ex) { HandleException(ex); throw; } } public async Task ExecuteAsync(Func<Task> operation) { try { await operation(); } catch (Exception ex) { HandleException(ex); throw; } } private void HandleException(Exception ex) { // Add your core error handling here: logging, metrics, etc. Console.WriteLine($"Library exception caught: {ex.GetType().Name} - {ex.Message}"); // Trigger the global "onError" event OnExceptionCaught?.Invoke(ex); } }
Instead of writing try/catch in every method, inject the IExceptionWrapper into your library classes and use it to wrap your business logic. This keeps your code clean and centralized.
public class UserManager { private readonly IExceptionWrapper _exceptionWrapper; // Inject the wrapper via constructor (works great with DI containers) public UserManager(IExceptionWrapper exceptionWrapper) { _exceptionWrapper = exceptionWrapper; } public void CreateUser(string username) { // No try/catch here—wrapper handles it _exceptionWrapper.Execute(() => { // Your business logic that might throw if (string.IsNullOrWhiteSpace(username)) throw new ArgumentException("Username cannot be empty"); Console.WriteLine($"User {username} created successfully"); }); } public async Task<User> GetUserAsync(int userId) { return await _exceptionWrapper.ExecuteAsync(async () => { // Async business logic if (userId <= 0) throw new ArgumentOutOfRangeException(nameof(userId), "User ID must be positive"); // Simulate database call await Task.Delay(100); return new User { Id = userId, Username = $"user{userId}" }; }); } } public class User { public int Id { get; set; } public string Username { get; set; } }
onError Handler In your application layer (where you consume the library), subscribe to the OnExceptionCaught event to handle errors globally—just like an onError callback.
// Initialize the wrapper (register as a singleton in DI for production use) var exceptionWrapper = new GlobalExceptionWrapper(); // Subscribe to the "onError" event exceptionWrapper.OnExceptionCaught += ex => { // Your custom error handling here: send to monitoring, show alerts, etc. Console.WriteLine($"Global onError triggered: {ex.GetType().Name} - {ex.Message}"); }; // Inject the wrapper into your library class var userManager = new UserManager(exceptionWrapper); // Use the class—exceptions will be caught and trigger the global handler try { userManager.CreateUser(""); } catch (ArgumentException) { // Optional: Caller can still handle specific exceptions if needed Console.WriteLine("Caller handled the empty username error"); } await userManager.GetUserAsync(-5);
If you want to eliminate even the Execute/ExecuteAsync calls, use Aspect-Oriented Programming (AOP) to automatically wrap all methods. For example, using Castle DynamicProxy:
public class ExceptionInterceptor : IInterceptor { private readonly Action<Exception> _onError; public ExceptionInterceptor(Action<Exception> onError) { _onError = onError; } public void Intercept(IInvocation invocation) { try { invocation.Proceed(); // Run the original method } catch (Exception ex) { _onError(ex); throw; } } } // Create a proxied version of your library class var proxyGenerator = new ProxyGenerator(); var interceptor = new ExceptionInterceptor(ex => Console.WriteLine($"AOP onError triggered: {ex.Message}")); var proxiedUserManager = proxyGenerator.CreateClassProxy<UserManager>(interceptor); // Now all method calls are automatically wrapped in exception handling proxiedUserManager.CreateUser("");
Key Notes
- Don’t swallow exceptions blindly: Re-throw unless you’re certain the error can be safely ignored.
- Thread safety: Ensure your wrapper is thread-safe if your library is used in multi-threaded environments.
- DI integration: Register
IExceptionWrapperas a singleton in your dependency injection container to ensure consistent error handling across all classes.
内容的提问来源于stack exchange,提问作者ercet00ilk




