创建一个与任务一起工作的异常转换动态代理

Create an exception-converting dynamic proxy that works with tasks

问题

我不知何故 运行 在圈子里...我尝试使用 Castle Dynamic Proxy 创建一个 interface proxy with target。代理应该

换句话说,拦截器应该捕获并转换特定的异常类型,而不是在所有其他情况下拦截。

我得到了这个用于同步方法的工作。但是,对于 return 任务的异步方法,我需要相同的行为。

我试过的

我尝试向 returned 任务添加延续并检查 IsFaultedException(类似于 this answer)。这适用于 return Task,但不适用于 return Task<T> 的方法,因为我的延续是 Task 类型(而且我不知道拦截器中的 T 是什么).

涵盖上述三种情况的异步方法测试 (XUnit.net)

public class ConvertNotFoundInterceptorTest
{
    [Fact]
    public void Non_throwing_func_returns_a_result()
    {
        Assert.Equal(43, RunTest(i => i + 1));
    }

    [Fact]
    public void InvalidOperationExceptions_are_converted_to_IndexOutOfRangeExceptions()
    {
        var exception = Assert.Throws<AggregateException>(() => RunTest(i => { throw new InvalidOperationException("ugh"); }));
        Assert.True(exception.InnerException is IndexOutOfRangeException);
    }

    [Fact]
    public void Other_exceptions_are_preserved()
    {
        var exception = Assert.Throws<AggregateException>(() => RunTest(i => { throw new ArgumentException("ugh"); }));
        Assert.True(exception.InnerException is ArgumentException);
    }

    private static int RunTest(Func<int, int> func)
    {
        var generator = new ProxyGenerator();

        var proxiedSubject = generator.CreateInterfaceProxyWithTarget<ISubject>(new Subject(func), new ConvertNotFoundInterceptor());

        return proxiedSubject.DoAsync(42).Result;
    }

    public interface ISubject
    {
        Task<int> DoAsync(int input);
    }

    public class Subject : ISubject
    {
        private readonly Func<int, int> _func;

        public Subject(Func<int, int> func)
        {
            _func = func;
        }

        public async Task<int> DoAsync(int input)
        {
            return await Task.Run(() => _func(input));
        }
    }
}

拦截器

public class ConvertNotFoundInterceptor : IInterceptor
{
    public void Intercept(IInvocation invocation)
    {
        invocation.Proceed();

        var task = invocation.ReturnValue as Task;
        if (task != null)
        {
            var continuation = task.ContinueWith(
                t =>
                {
                    if (t.Exception != null && t.Exception.InnerException is InvalidOperationException)
                    {
                        throw new IndexOutOfRangeException();
                    }
                }, TaskContinuationOptions.OnlyOnFaulted);

            // The following line fails (InvalidCastException: Unable to cast object 
            // of type 'System.Threading.Tasks.ContinuationTaskFromTask' 
            // to type 'System.Threading.Tasks.Task`1[System.Int32]'.)
            invocation.ReturnValue = continuation;
        }
    }
}

请注意,此处所示的实现不考虑同步情况。我故意把那部分漏掉了。

问题

将上述拦截逻辑添加到异步方法中的正确方法是什么?

好的,这不适用于 Task<dynamic>,因为 Castle Dynamic Proxy 要求 ReturnValue 是完全匹配的类型。但是,您可以通过使用 dynamic 来相当优雅地完成此任务:

public class ConvertNotFoundInterceptor : IInterceptor
{
    public void Intercept(IInvocation invocation)
    {
        invocation.Proceed();

        var task = invocation.ReturnValue as Task;
        if (task != null)
            invocation.ReturnValue = ConvertNotFoundAsync((dynamic)task);
    }

    private static async Task ConvertNotFoundAsync(Task source)
    {
        try
        {
            await source.ConfigureAwait(false);
        }
        catch (InvalidOperationException)
        {
            throw new IndexOutOfRangeException();
        }
    }

    private static async Task<T> ConvertNotFoundAsync<T>(Task<T> source)
    {
        try
        {
            return await source.ConfigureAwait(false);
        }
        catch (InvalidOperationException)
        {
            throw new IndexOutOfRangeException();
        }
    }
}

我非常喜欢 async/await 语法,因为它们可以正确处理使用 ContinueWith.

时比较棘手的边缘情况