创建一个与任务一起工作的异常转换动态代理
Create an exception-converting dynamic proxy that works with tasks
问题
我不知何故 运行 在圈子里...我尝试使用 Castle Dynamic Proxy 创建一个 interface proxy with target。代理应该
- Return 如果没有抛出异常(即什么也不做),调用的 return 值。
- 如果调用抛出
InvalidOperationException
. ,则抛出新的 InterceptedException
- 如果调用抛出另一个异常则抛出
e
e
。
换句话说,拦截器应该捕获并转换特定的异常类型,而不是在所有其他情况下拦截。
我得到了这个用于同步方法的工作。但是,对于 return 任务的异步方法,我需要相同的行为。
我试过的
我尝试向 returned 任务添加延续并检查 IsFaulted
和 Exception
(类似于 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
.
时比较棘手的边缘情况
问题
我不知何故 运行 在圈子里...我尝试使用 Castle Dynamic Proxy 创建一个 interface proxy with target。代理应该
- Return 如果没有抛出异常(即什么也不做),调用的 return 值。
- 如果调用抛出
InvalidOperationException
. ,则抛出新的 - 如果调用抛出另一个异常则抛出
e
e
。
InterceptedException
换句话说,拦截器应该捕获并转换特定的异常类型,而不是在所有其他情况下拦截。
我得到了这个用于同步方法的工作。但是,对于 return 任务的异步方法,我需要相同的行为。
我试过的
我尝试向 returned 任务添加延续并检查 IsFaulted
和 Exception
(类似于 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
.