摆脱嵌套的 using(...) 语句

发布于 2024-09-01 09:33:21 字数 1313 浏览 8 评论 0原文

有时我需要在一个函数中使用多个一次性对象。最常见的情况是拥有 StreamReader 和 StreamWriter,但有时甚至不止于此。

嵌套的 using 语句很快就会堆积起来并且看起来很难看。 为了解决这个问题,我创建了一个小类,它收集 IDisposable 对象并在其本身被释放时释放它们。

public class MultiDispose : HashSet<IDisposable>, IDisposable
{
    public MultiDispose(params IDisposable[] objectsToDispose)
    {
        foreach (IDisposable d in objectsToDispose)
        {
            this.Add(d);
        }
    }

    public T Add<T>(T obj) where T : IDisposable
    {
        base.Add(obj);
        return obj;
    }

    public void DisposeObject(IDisposable obj)
    {
        obj.Dispose();
        base.Remove(obj);
    }


    #region IDisposable Members

    public void Dispose()
    {
        foreach (IDisposable d in this)
        {
            d.Dispose();
        }

    }

    #endregion
}

所以我的代码现在看起来像这样:

        using (MultiDispose md = new MultiDispose())
        {
            StreamReader rdr = md.Add(new StreamReader(args[0]));
            StreamWriter wrt = md.Add(new StreamWriter(args[1]));
            WhateverElseNeedsDisposing w = md.Add(new WhateverElseNeedsDisposing());

            // code
        }

这种方法有什么问题可能会导致问题吗? 我特意保留了从 HashSet 继承的 Remove 函数,以便该类更加灵活。 当然,滥用这个函数可能会导致对象没有被正确处理,但是如果没有这个类,还有很多其他的方法会搬起石头砸自己的脚。

Sometimes I need to use several disposable objects within a function. Most common case is having StreamReader and StreamWriter but sometimes it's even more than this.

Nested using statements quickly add up and look ugly.
To remedy this I've created a small class that collects IDisposable objects and disposes of them when it itself is disposed.

public class MultiDispose : HashSet<IDisposable>, IDisposable
{
    public MultiDispose(params IDisposable[] objectsToDispose)
    {
        foreach (IDisposable d in objectsToDispose)
        {
            this.Add(d);
        }
    }

    public T Add<T>(T obj) where T : IDisposable
    {
        base.Add(obj);
        return obj;
    }

    public void DisposeObject(IDisposable obj)
    {
        obj.Dispose();
        base.Remove(obj);
    }


    #region IDisposable Members

    public void Dispose()
    {
        foreach (IDisposable d in this)
        {
            d.Dispose();
        }

    }

    #endregion
}

So my code now looks like this:

        using (MultiDispose md = new MultiDispose())
        {
            StreamReader rdr = md.Add(new StreamReader(args[0]));
            StreamWriter wrt = md.Add(new StreamWriter(args[1]));
            WhateverElseNeedsDisposing w = md.Add(new WhateverElseNeedsDisposing());

            // code
        }

Is there anything wrong with this approach that can cause problems down the road?
I left the Remove function inherited from the HashSet on purpose so that the class would be more flexible.
Surely misusing this function can lead to objects not being disposed of properly, but then there many other ways to shoot yourself in the foot without this class.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

一人独醉 2024-09-08 09:33:21

你可以这样做:

using (var a = new A())
using (var b = new B())
{
    /// ...
}

You could just do this:

using (var a = new A())
using (var b = new B())
{
    /// ...
}
墨离汐 2024-09-08 09:33:21

关于一般原则的几点:

  • 您的代码明显不符合惯用的 C# 风格。基本上,您要求使用您的代码的其他人采用一种不寻常的风格,但几乎没有什么好处。
  • 正如其他人指出的那样,您可以嵌套 using 语句而无需额外的大括号
  • 如果您发现自己在单个方法中有很多 using 语句,您可能需要考虑将其分解为较小的方法
  • 如果有两个相同类型的变量,则可以使用单个 using 语句:

    using (Stream input = File.OpenRead("input.dat"),
           输出 = File.OpenWrite("output.dat"))
    {
    }
    

现在假设您确实想继续执行此操作:

  • 您的代码将以难以处理的方式处置其包含的资源。 - 预测顺序。它应该嵌入一个列表,而不是使用集合 - 然后以与 Add 调用相反的顺序处理内容。
  • 没有理由从 HashSet 或任何集合派生。您应该在类中拥有一个列表作为私有成员变量。
  • 如果其中一个 Dispose 调用失败,则不会进行任何其他 Dispose 调用;使用传统的 using 语句,每次对 Dispose 的调用都是在其自己的 finally 块中进行的。

基本上,我认为这是一个坏主意。嵌套两层深一点也不痛苦;嵌套三个应该很少见;嵌套四个或更多强烈建议重构。您应该在设计时远离它,而不是试图应对深度嵌套的痛苦。

A few points about the general principle:

  • Your code is distinctly non-idiomatic C#. Basically you're asking anyone else who works with your code to take on board an unusual style for very little benefit.
  • As others have pointed out, you can nest using statements without extra braces
  • If you find yourself with lots of using statements in a single method, you might want to consider breaking it into smaller methods
  • If you have two variables of the same type, you can use a single using statement:

    using (Stream input = File.OpenRead("input.dat"),
           output = File.OpenWrite("output.dat"))
    {
    }
    

Now assuming you really want to go ahead with this:

  • Your code will dispose of its contained resources in a hard-to-predict order. Instead of using a set, it should embed a list - and then dispose of things in the reverse order to the calls to Add.
  • There is no reason to derive from HashSet<T> or indeed any collection. You should just have a list within the class as a private member variable.
  • If one of the Dispose calls fails, none of the other Dispose calls will be made; with a traditional using statement, each call to Dispose is made within its own finally block.

Basically, I think it's a bad idea. Nesting two levels deep is far from painful; nesting three should be rare; nesting four or more strongly suggests refactoring. Rather than trying to cope with the pain of deep nesting, you should design away from it.

最冷一天 2024-09-08 09:33:21

也许您只是展示了一个简单的示例,但我认为以下内容更具可读性。

 using (StreamReader rdr = new StreamReader(args[0])) 
 using (StreamWriter wrt = new StreamWriter(args[1])) 
 {     
   // code 
 }

Maybe it is just that you have shown a simple example, but I think the following is more readable.

 using (StreamReader rdr = new StreamReader(args[0])) 
 using (StreamWriter wrt = new StreamWriter(args[1])) 
 {     
   // code 
 }
赠意 2024-09-08 09:33:21

您可以仅使用一对大括号来使嵌套的 using 语句更漂亮,如下所示:

using (StreamReader rdr = new StreamReader(args[0])) 
using (StreamWriter wrt = new StreamWriter(args[1])) 
{
    ///...
}

要回答您的问题,您需要以相反的加法顺序进行处理。
因此,您不能使用HashSet

此外,没有理由向外界公开 IDisposable 列表。
因此,您不应继承任何集合类,而应维护一个私有 List

然后,您应该具有公共 AddDispose 方法(并且没有其他方法),并在 Dispose 中向后循环列表。

You can make nested using statements prettier by only using one pair of braces, like this:

using (StreamReader rdr = new StreamReader(args[0])) 
using (StreamWriter wrt = new StreamWriter(args[1])) 
{
    ///...
}

To answer your question, you need to dispose in the opposite order of addition.
Therefore, you cannot use a HashSet.

Also, there is no reason to expose the list of IDisposables to the outside world.
Therefore, you should not inherit any collection class, and instead maintain a private List<IDisposable>.

You should then have public Add<T> and Dispose methods (and no other methods), and loop through the list backwards in Dispose.

日记撕了你也走了 2024-09-08 09:33:21

就我个人而言,这会让我发疯。如果您发现嵌套的 using 语句很烦人,您可以恢复到 try/finally 语法。 Dispose 方法不应抛出异常,因此您可以假设多个一次性对象不需要单独包装在 try/finally 块中。

另外值得注意的是,您只需要一组括号来表示相邻的 using 块,例如:

using (var stream = File.Open(...))
using (var reader = new StreamReader(stream)) {

   // do stuff

}

Personally this would drive me nuts. If you are finding nested using statements to be annoying, you could revert to the try/finally syntax. Dispose methods are not supposed to throw exceptions so you could assume that multiple disposables would not need to be individually wrapped in try/finally blocks.

Also worth noting is that you only need one set of brackets for adjacent using blocks like:

using (var stream = File.Open(...))
using (var reader = new StreamReader(stream)) {

   // do stuff

}
夜光 2024-09-08 09:33:21

我不得不说,我不同意那些想要一个接一个地使用语句的人,例如:

using (var a = new StreamReader())
using (var b = new StreamWriter())
{
 // Implementation
}

在我看来,这是非常不可读的 - 任何未包装的代码块都是糟糕的风格,并且可能会导致问题,除非所有从事此工作的开发人员都非常小​​心。

我会将其与以下内容相提并论:

if (someBoolean) DoSomething();
{
  // Some code block unrelated to the if statement
}

从技术上讲,它并不是无效的,但看起来很糟糕。

我同意 MultiDispose 概念可能不是最好的想法,因为它不是一种可接受的模式,但我也绝对不会走这条路。如果您无法将事物分解成更小的块,那么我建议您只使用嵌套的用法。

I've got to say I disagree with the people who want to do using statements one after the other like:

using (var a = new StreamReader())
using (var b = new StreamWriter())
{
 // Implementation
}

In my opinion, that's very unreadable - having any block of code that's not wrapped is just bad style, and may lead to problems unless all developers working on it are very careful.

I'd put that on par with something like:

if (someBoolean) DoSomething();
{
  // Some code block unrelated to the if statement
}

Technically it's not invalid, but it's awful to look at.

I agree that the MultiDispose concept is probably not the best idea, due to the fact that it's not an accepted pattern, but I definitely wouldn't go this route either. If you can't break things up into smaller chunks, then I'd suggest just living with the nested usings.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文