如何重构嵌套使用中的代码?

时间:2012-04-23 17:43:04

标签: c# refactoring using-statement code-duplication

我有一些代码有很多重复。问题来自于我正在处理嵌套的IDisposable类型。今天我有一些看起来像:

public void UpdateFromXml(Guid innerId, XDocument someXml)
{
    using (var a = SomeFactory.GetA(_uri))
    using (var b = a.GetB(_id))
    using (var c = b.GetC(innerId))
    {
        var cWrapper = new SomeWrapper(c);
        cWrapper.Update(someXml);
    }
}

public bool GetSomeValueById(Guid innerId)
{
    using (var a = SomeFactory.GetA(_uri))
    using (var b = a.GetB(_id))
    using (var c = b.GetC(innerId))
    {
        return c.GetSomeValue();
    }
}

对于这些方法中的每一个,整个嵌套using块都是相同的(显示了两个,但是大约有十个)。唯一不同的是当你到达using块的内层时会发生什么。

我想的一种方法是做一些事情:

public void UpdateFromXml(Guid innerId, XDocument someXml)
{
    ActOnC(innerId, c =>
    { 
        var cWrapper = new SomeWrapper(c);
        cWrapper.Update(someXml);
    });
}

public bool GetSomeValueById(Guid innerId)
{
    var result = null;

    ActOnC(innerId, c => { result = c.GetSomeValue(); });

    return result;
}

private void ActOnC(Guid innerId, Action<TheCType> action)
{
    using (var a = SomeFactory.GetA(_uri))
    using (var b = a.GetB(_id))
    using (var c = b.GetC(innerId))
    {
        action(c);
    }        
}

这是有效的,它只是一种笨重的解析(作为人类)。 是否有人对如何减少围绕此类嵌套using块的代码重复有任何其他建议?如果它们不是IDisposable那么可能只会创建一个方法返回b.GetC(innerId)的结果......但这不是这种情况。

4 个答案:

答案 0 :(得分:1)

在Rx框架中有一个名为CompositeDisposable http://msdn.microsoft.com/en-us/library/system.reactive.disposables.compositedisposable%28v=vs.103%29.aspx

的类

不应该太难推出自己的(虽然非常精简版):

public class CompositeDisposable : IDisposable
{
    private IDisposable[] _disposables;

    public CompositeDisposable(params IDisposable[] disposables)
    {
        _disposables = disposables;
    }

    public void Dispose()
    {
        if(_disposables == null)
        {
            return;
        }

        foreach(var disposable in _disposables)
        {
            disposable.Dispose();
        }
    }
}

然后这看起来更清洁:

public void UpdateFromXml(Guid innerId, XDocument someXml)
{
    var a = SomeFactory.GetA(_uri);
    var b = a.GetB(_id);
    var c = b.GetC(innerId);
    using(new CompositeDisposable(a,b,c))
    {
        var cWrapper = new SomeWrapper(c);
        cWrapper.Update(someXml);
    }
}

答案 1 :(得分:1)

我喜欢BFree提供的答案作为开头,但我做了一些修改。

//Give it a better name; this isn't designed to be a general purpose class
public class MyCompositeDisposable : IDisposable 
{
    public MyCompositeDisposable (string uri, int id, int innerid)
    {
        A = SomeFactory.GetA(uri);
        B = A.GetB(id);
        C = B.GetC(innerId);
    }

    //You can make A & B private if appropriate; 
    //not sure if all three or just C should be exposed publicly.
    //Class names are made up; you'll need to fix.  
    //They should also probably be given more meaningful names.
    public ClassA A{get;private set;}
    public ClassB B{get;private set;}
    public ClassC C{get;private set;}

    public void Dispose()
    {
        A.Dispose();
        B.Dispose();
        C.Dispose();
    }
}

完成后,您可以执行以下操作:

public bool GetSomeValueById(Guid innerId)
{
    using(MyCompositeDisposable d = new MyCompositeDisposable(_uri, _id, innerId))
    {
        return d.C.GetSomeValue();
    }
}

请注意,MyCompositeDisposable可能需要在构造函数和Dispose方法中使用try / finally块,以便创建/销毁中的错误可以正确地确保没有任何内容被取消。

答案 2 :(得分:1)

您始终可以创建更大的上下文来管理应创建/处置的对象。然后编写一个方法来创建更大的上下文...

public class DisposeChain<T> : IDisposable where T : IDisposable
{
    public T Item { get; private set; }
    private IDisposable _innerChain;

    public DisposeChain(T theItem)
    {
        this.Item = theItem;
        _innerChain = null;
    }

    public DisposeChain(T theItem, IDisposable inner)
    {
        this.Item = theItem;
        _innerChain = inner;
    }

    public DisposeChain<U> Next<U>(Func<T, U> getNext) where U : IDisposable
    {
        try
        {
            U nextItem = getNext(this.Item);
            DisposeChain<U> result = new DisposeChain<U>(nextItem, this);
            return result;
        }
        catch  //an exception occurred - abort construction and dispose everything!
        {
            this.Dispose()
            throw;
        }
    }

    public void Dispose()
    {
        Item.Dispose();
        if (_innerChain != null)
        {
            _innerChain.Dispose();
        }
    }
}

然后使用它:

    public DisposeChain<DataContext> GetCDisposeChain()
    {
        var a = new DisposeChain<XmlWriter>(XmlWriter.Create((Stream)null));
        var b = a.Next(aItem => new SqlConnection());
        var c = b.Next(bItem => new DataContext(""));

        return c;
    }

    public void Test()
    {
        using (var cDisposer = GetCDisposeChain())
        {
            var c = cDisposer.Item;
            //do stuff with c;
        }
    }

答案 3 :(得分:0)

如果您的Dispoable类型正确处置了所有可支配成员,则只需要一个使用声明。

例如,这个:

public bool GetSomeValueById(Guid innerId)
{
    using (var a = SomeFactory.GetA(_uri))
    using (var b = a.GetB(_id))
    using (var c = b.GetC(innerId))
    {
        return c.GetSomeValue();
    }
}
如果a类型为b和c的成员,并且在其处置方法中处理了b和c,

可能会成为这个:

public bool GetSomeValueById(Guid innerId)
{
    using (var a = SomeFactory.GetA(_uri))
    {
        return a.GetSomeValue();
    }
}

class A : IDisposable
{
  private a;
  private b;

  public A (B b, C c)
  {
     this.b = b; this.c = c;
  }

  public void Dispose()
  {
     Dispose(true);
  }

  protected void Dispose(bool disposing)
  {
     if (disposing)
     {
        b.Dispose();
        c.Dispose();
     }
  }
}

然而,您必须修改工厂以将b和c注入a。