【问题标题】:How could one refactor code involved in nested usings?如何重构嵌套使用中涉及的代码?
【发布时间】:2012-04-23 17:43:04
【问题描述】:

我的一些代码有很多重复。问题来自于我正在处理嵌套的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) ...但这里不是这样。

【问题讨论】:

  • +1 我没有在您的解决方案中看到任何笨拙的东西。它是一种非正统的比程序更实用的方法,但我认为它是专业的,而不是缺点
  • 我认为您的实现看起来不错,但也许您会更喜欢下面提供的一些替代方案。如果您发现自己不得不捆绑大量一次性用品,您可能需要重新设计一些东西,以免最终陷入这种情况。

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


【解决方案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 块,以便创建/销毁中的错误正确确保没有任何东西最终未处置。

【讨论】:

  • 像这样将它全部包装在一个类中的想法非常适合我的需求,并为我的所有案例提供了代码重复数据删除和灵活性的正确平衡,而且它甚至有助于分离有点担心。这几乎是所有答案中最好的。谢谢。
  • 这与 BFree 的答案具有相同的缺陷 - 在 C 的构造过程中出现异常将使 A 和 B 未被处理。
  • @DavidB 我已经在答案的末尾注意到需要进行这种错误检查,但这里的答案中没有包含它。如果在 OP 的情况下需要它,他知道他需要添加它。
【解决方案2】:

在 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);
    }
}

【讨论】:

  • 如果在 b.GetC 期间发生异常怎么办 - 我认为发生这种情况时 a 和 b 没有正确处理。
【解决方案3】:

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

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;
        }
    }

【讨论】:

    【解决方案4】:

    如果您的Dispoable 类型正确处理所有一次性成员,您只需要一个 using 语句。

    例如,这个:

    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 的成员,并且 a 在其 dispose 方法中处理了 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。

    【讨论】:

    • 在让对象处理由另一个类提供给它们的对象时,您应该小心。如果多个实例依赖于该对象怎么办?处置通常应该是拥有类的责任,在这种情况下A 不拥有bc
    • @Thomas 好点。通常,您还会有布尔 ctor 参数来指示 A 是否拥有 b 和 c。
    猜你喜欢
    • 1970-01-01
    • 2022-06-11
    • 2011-01-03
    • 2022-01-24
    • 1970-01-01
    • 2016-01-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多