You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何编程捕获接口类库所有错误?实现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.

Step 1: Define the 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;
}
Step 2: Implement the Global Exception Wrapper

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);
    }
}
Step 3: Integrate the Wrapper into Your Library Classes

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; }
}
Step 4: Hook Up the Global 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);
Bonus: AOP for Even Less Code

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 IExceptionWrapper as a singleton in your dependency injection container to ensure consistent error handling across all classes.

内容的提问来源于stack exchange,提问作者ercet00ilk

火山引擎 最新活动