С# Обернуть вызовы различных методов класса в единый try-catch

Абстрактный пример. Есть клиент со следующим интерфейсом:

public interface IUserClient
{
    Task<IUser> GetUserByIdAsync(int id);
    Task<IUser> FindUserByFirstNameAndLastNameAsync(string firstName, string lastName);
    Task<IUser> CreateUserAsync(string firstName, string lastName, string phone);
}

public interface IUser
{
    int Id { get; }
    string FirstName { get; set; }
    string LastName { get; set; }
    string Phone { get; set; }
}

Мне нужно написать обертку над этим клиентом, который будет пытаться перелогиниться, если метод клиента выбросит определенный Exception. Пока я реализовал это примерно так:

public class UserClientWrapper : IUserClient
{
    private readonly IUserClient _userClient;

    public UserClientWrapper(IUserClient userClient)
    {
        _userClient = userClient;
    }

    public async Task<IUser> GetUserByIdAsync(int id)
    {
        try
        {
            return await _userClient.GetUserByIdAsync(id);
        }
        catch (UnauthorizedAccessException)
        {
            // Пытаемся перелогиниться

            try
            {
                return await _userClient.GetUserByIdAsync(id);
            }
            catch (UnauthorizedAccessException)
            {
                // Пишем в лог, что что-то не так, и пробрасываем выше этот exception
                throw;
            }
        }
    }

    public async Task<IUser> FindUserByFirstNameAndLastNameAsync(string firstName, string lastName)
    {
        try
        {
            return await _userClient.FindUserByFirstNameAndLastNameAsync(firstName, lastName);
        }
        catch (UnauthorizedAccessException)
        {
            // Пытаемся перелогиниться

            try
            {
                return await _userClient.FindUserByFirstNameAndLastNameAsync(firstName, lastName);
            }
            catch (UnauthorizedAccessException)
            {
                // Пишем в лог, что что-то не так, и пробрасываем выше этот exception
                throw;
            }
        }
    }

    public async Task<IUser> CreateUserAsync(string firstName, string lastName, string phone)
    {
        // и тут тоже самое
    }
}

Но вот смущает то, что я в каждом методе обязан дублировать код try-catch. Хотелось бы эту логику поместить в отдельный метод, но я не знаю, возможно ли это вообще. Поэтому возникает вопрос: как избежать дублирования кода в данном случае?

P.S. Важно! Методы клиента разные, с разными сигнатурами и возвращаемыми типами, но все они асинхронные.


Ответы (1 шт):

Автор решения: Andrei Khotko

Нашел ответ на англоязычном StackOverflow. Вынес логику обработки исключений в метод, в который передаю делегат Func<Task<T>>

public class UserClientWrapper : IUserClient
{
    private readonly IUserClient _userClient;

    public UserClientWrapper(IUserClient userClient)
    {
        _userClient = userClient;
    }

    public Task<IUser> GetUserByIdAsync(int id)
    {
        return WrapClientCallAsync(() => _userClient.GetUserByIdAsync(id));
    }

    public Task<IUser> FindUserByFirstNameAndLastNameAsync(string firstName, string lastName)
    {
        return WrapClientCallAsync(() => _userClient.FindUserByFirstNameAndLastNameAsync(firstName, lastName));
    }

    public Task<IUser> CreateUserAsync(string firstName, string lastName, string phone)
    {
        return WrapClientCallAsync(() => _userClient.CreateUserAsync(firstName, lastName, phone));
    }

    private async Task<T> WrapClientCallAsync<T>(Func<Task<T>> function)
    {
        try
        {
            return await function();
        }
        catch (UnauthorizedAccessException)
        {
            // Пытаемся перелогиниться

            try
            {
                return await function();
            }
            catch (UnauthorizedAccessException)
            {
                // Пишем в лог, что что-то не так, и пробрасываем выше этот exception
                throw;
            }
        }
    }
}
→ Ссылка