如何为返回 IEnumerable 的方法编写拦截器



我编写了简单的拦截器(使用Castle.DynamicProxy)来处理数据库连接生命周期。 即所有服务都具有连接属性,该属性在首次使用时打开新的服务。连接在每次方法调用后自动关闭:

class CloseConnectionInterceptor : IInterceptor
{
    public void Intercept(IInvocation invocation)
    {
        try
        {
            invocation.Proceed();
        }
        finally
        {
            var hasConnection = invocation.InvocationTarget as IHasConnection;
            if (hasConnection.IsConnectionOpen)
            {
                hasConnection.Connection.Dispose();
                hasConnection.Connection = null;
            }
        }
    }
}
interface IHasConnection
{
    bool IsConnectionOpen { get; }
    Connection Connection { get; set; }
}

除了返回迭代器的方法外,它运行良好:

[Test]
public void ConnectionClosedByProxy()
{
    // arrange
    var service = new MyService();
    var proxy = new ProxyGenerator()
        .CreateInterfaceProxyWithTarget<IMyService>(
            service, new CloseConnectionInterceptor());
    // act
    proxy.GetList();
    // assert: works!!!
    Assert.IsFalse(service.IsConnectionOpen);
}
[Test]
public void IteratorLeavesOpenConnection()
{
    // arrange
    var service = new MyService();
    var proxy = new ProxyGenerator()
        .CreateInterfaceProxyWithTarget<IMyService>(
            service, new CloseConnectionInterceptor());
    // act
    proxy.GetEnumerable().ToList();
    // assert: bad, bad, bad!
    Assert.IsTrue(service.IsConnectionOpen);
}

在此处查看完整示例:https://gist.github.com/4087483

如果在我的 GetEnumerable 方法中有"using (new Connection())"语句,那么它按预期工作 - 连接在上次访问迭代器后关闭。是否有可能在拦截器中捕捉到这一刻?还是我应该不仅代理方法,还代理生成的 IEnumerable?

我把答案的种子放在我的问题:)。这是修改后的拦截器,其代理也产生 IEnumerable:

class CloseConnectionInterceptor : IInterceptor
{
    public void Intercept(IInvocation invocation)
    {
        Type genericType = invocation.Method.ReturnType.IsGenericType
            ? invocation.Method.ReturnType.GetGenericTypeDefinition()
            : null;
        if (genericType == typeof(IEnumerable<>))
        {
            invocation.Proceed();
            var method = GetType()
                .GetMethod("HandleIteratorInvocation")
                .MakeGenericMethod(
                    invocation.Method.ReturnType.GetGenericArguments()[0]);
            invocation.ReturnValue = method.Invoke(
                null,
                new[] { invocation.InvocationTarget, invocation.ReturnValue });
        }
        else
        {
            HandleNonIteratorInvocation(invocation);
        }
    }
    public static IEnumerable<T> HandleIteratorInvocation<T>(
        IHasConnection hasConnection, IEnumerable enumerable)
    {
        try
        {
            foreach (var element in enumerable)
                yield return (T)element;
        }
        finally
        {
            CloseOpenConnection(hasConnection);
        }
    }
    private static void HandleNonIteratorInvocation(IInvocation invocation)
    {
        try
        {
            invocation.Proceed();
        }
        finally
        {
            CloseOpenConnection(invocation.InvocationTarget as IHasConnection);
        }
    }
    private static void CloseOpenConnection(IHasConnection hasConnection)
    {
        if (hasConnection.IsConnectionOpen)
        {
            hasConnection.Connection.Dispose();
            hasConnection.Connection = null;
        }
    }
}

最新更新