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

时间:2012-11-16 14:10:11

标签: c# ienumerable idisposable castle-dynamicproxy

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

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?

1 个答案:

答案 0 :(得分:0)

我把答案的种子放在我的问题中:)。这是修改过的拦截器,代理也产生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;
        }
    }
}