C# “使用”语句与“最终尝试”

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/278902/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-03 21:18:45  来源:igfitidea点击:

'using' statement vs 'try finally'

c#.netmultithreadingusing-statement

提问by Jeremy

I've got a bunch of properties which I am going to use read/write locks on. I can implement them either with a try finallyor a usingclause.

我有一堆要使用读/写锁的属性。我可以用 atry finally或 ausing子句实现它们。

In the try finallyI would acquire the lock before the try, and release in the finally. In the usingclause, I would create a class which acquires the lock in its constructor, and releases in its Dispose method.

try finallyI 中,我会在 之前获取锁try,并在 中释放finally。在using子句中,我将创建一个类,该类在其构造函数中获取锁,并在其 Dispose 方法中释放。

I'm using read/write locks in a lot of places, so I've been looking for ways that might be more concise than try finally. I'm interested in hearing some ideas on why one way may not be recommended, or why one might be better than another.

我在很多地方都使用读/写锁,所以我一直在寻找比try finally. 我很想听听一些关于为什么不推荐一种方法,或者为什么一种方法可能比另一种更好的想法。

Method 1 (try finally):

方法一(try finally):

static ReaderWriterLock rwlMyLock_m  = new ReaderWriterLock();
private DateTime dtMyDateTime_m
public DateTime MyDateTime
{
    get
    {
        rwlMyLock_m .AcquireReaderLock(0);
        try
        {
            return dtMyDateTime_m
        }
        finally
        {
            rwlMyLock_m .ReleaseReaderLock();
        }
    }
    set
    {
        rwlMyLock_m .AcquireWriterLock(0);
        try
        {
            dtMyDateTime_m = value;
        }
        finally
        {
            rwlMyLock_m .ReleaseWriterLock();
        }
    }
}

Method 2:

方法二:

static ReaderWriterLock rwlMyLock_m  = new ReaderWriterLock();
private DateTime dtMyDateTime_m
public DateTime MyDateTime
{
    get
    {
        using (new ReadLock(rwlMyLock_m))
        {
            return dtMyDateTime_m;
        }
    }
    set
    {
        using (new WriteLock(rwlMyLock_m))
        {
            dtMyDateTime_m = value;
        }
    }
}

public class ReadLock : IDisposable
{
    private ReaderWriterLock rwl;
    public ReadLock(ReaderWriterLock rwl)
    {
        this.rwl = rwl;
        rwl.AcquireReaderLock(0);
    }

    public void Dispose()
    {
        rwl.ReleaseReaderLock();
    }
}

public class WriteLock : IDisposable
{
    private ReaderWriterLock rwl;
    public WriteLock(ReaderWriterLock rwl)
    {
        this.rwl = rwl;
        rwl.AcquireWriterLock(0);
    }

    public void Dispose()
    {
        rwl.ReleaseWriterLock();
    }
}

采纳答案by chakrit

From MSDN, using Statement (C# Reference)

从 MSDN,使用语句(C# 参考)

The using statement ensures that Dispose is called even if an exception occurs while you are calling methods on the object. You can achieve the same result by putting the object inside a try block and then calling Dispose in a finally block; in fact, this is how the using statement is translated by the compiler. The code example earlier expands to the following code at compile time (note the extra curly braces to create the limited scope for the object):

using 语句确保 Dispose 被调用,即使在您调用对象的方法时发生异常。您可以通过将对象放在 try 块中,然后在 finally 块中调用 Dispose 来获得相同的结果;事实上,这就是编译器如何翻译 using 语句。前面的代码示例在编译时扩展为以下代码(注意额外的大括号以创建对象的有限范围):

{
  Font font1 = new Font("Arial", 10.0f);
  try
  {
    byte charset = font1.GdiCharSet;
  }
  finally
  {
    if (font1 != null)
      ((IDisposable)font1).Dispose();
  }
}

So basically, it is the same code but with a nice automatic null-checks and an extra scope for your variable. The documentation also states that it "ensures the correct use of IDisposable object" so you might as well gets even better framework support for any obscure cases in the future.

所以基本上,它是相同的代码,但有一个很好的自动空检查和一个额外的变量范围。该文档还指出,它“确保正确使用 IDisposable 对象”,因此您将来可能会为任何晦涩的情况获得更好的框架支持。

So go with option 2.

所以选择选项 2。

Having the variable inside a scopethat ends immediately after it's no longer needed is also a plus.

将变量放在不再需要它后立即结束的范围内也是一个加号。

回答by Rob Walker

I definitely prefer the second method. It is more concise at the point of usage, and less error prone.

我绝对更喜欢第二种方法。它在使用点上更加简洁,并且不易出错。

In the first case someone editing the code has to be careful not to insert anything between the Acquire(Read|Write)Lock call and the try.

在第一种情况下,编辑代码的人必须小心不要在 Acquire(Read|Write)Lock 调用和 try 之间插入任何内容。

(Using a read/write lock on individual properties like this is usually overkill though. They are best applied at a much higher level. A simple lock will often suffice here since the possibility of contention is presumably very small given the time the lock is held for, and acquiring a read/write lock is a more expensive operation than a simple lock).

(不过,在像这样的单个属性上使用读/写锁通常是矫枉过正的。它们最好在更高的级别应用。简单的锁在这里通常就足够了,因为考虑到持有锁的时间,争用的可能性可能非常小获取读/写锁是一个比简单锁更昂贵的操作)。

回答by Nathen Silver

I think method 2 would be better.

我认为方法2会更好。

  • Simpler and more readable code in your properties.
  • Less error-prone since the locking code doesn't have to be re-written several times.
  • 属性中更简单、更易读的代码。
  • 由于无需多次重写锁定代码,因此不易出错。

回答by Tetha

DRYsays: second solution. The first solution duplicates the logic of using a lock, whereas the second does not.

DRY说:第二种解决方案。第一个解决方案复制了使用锁的逻辑,而第二个没有。

回答by Luke

Try/Catch blocks are generally for exception handling, while using blocks are used to ensure that the object is disposed.

Try/Catch 块一般用于异常处理,而 using 块用于确保对象被释放。

For the read/write lock a try/catch might be the most useful, but you could also use both, like so:

对于读/写锁,try/catch 可能是最有用的,但您也可以同时使用两者,如下所示:

using (obj)
{
  try { }
  catch { }
}

so that you can implicitly call your IDisposable interface as well as make exception handling concise.

以便您可以隐式调用 IDisposable 接口并使异常处理简洁。

回答by Luke

I like the 3rd option

我喜欢第三个选项

private object _myDateTimeLock = new object();
private DateTime _myDateTime;

public DateTime MyDateTime{
  get{
    lock(_myDateTimeLock){return _myDateTime;}
  }
  set{
    lock(_myDateTimeLock){_myDateTime = value;}
  }
}

Of your two options, the second option is the cleanest and easier to understand what's going on.

在您的两个选项中,第二个选项是最清晰、更容易理解的。

回答by Steven A. Lowe

Consider the possibility that both solutions are bad because they mask exceptions.

考虑两种解决方案都不好的可能性,因为它们掩盖了异常

A trywithout a catchshould obviously be a bad idea; see MSDNfor why the usingstatement is likewise dangerous.

Atry没有 acatch显然应该是一个坏主意;请参阅MSDN了解为什么该using语句同样危险。

Note also Microsoft now recommends ReaderWriterLockSliminstead of ReaderWriterLock.

另请注意,Microsoft 现在推荐ReaderWriterLockSlim而不是 ReaderWriterLock。

Finally, note that the Microsoft examples use two try-catch blocksto avoid these issues, e.g.

最后,请注意 Microsoft 示例使用两个 try-catch 块来避免这些问题,例如

try
{
    try
    {
         //Reader-writer lock stuff
    }
    finally
    {
         //Release lock
    }
 }
 catch(Exception ex)
 {
    //Do something with exception
 }

A simple, consistent, clean solution is a good goal, but assuming you can't just use lock(this){return mydateetc;}, you might reconsider the approach; with more info I'm sure Stack Overflow can help ;-)

一个简单、一致、干净的解决方案是一个很好的目标,但假设您不能只使用lock(this){return mydateetc;},您可能会重新考虑该方法;有了更多信息,我相信 Stack Overflow 可以提供帮助;-)

回答by Rob Williams

I personally use the C# "using" statement as often as possible, but there are a few specific things that I do along with it to avoid the potential issues mentioned. To illustrate:

我个人尽可能频繁地使用 C#“using”语句,但我会同时做一些特定的事情来避免提到的潜在问题。为了显示:

void doSomething()
{
    using (CustomResource aResource = new CustomResource())
    {
        using (CustomThingy aThingy = new CustomThingy(aResource))
        {
            doSomething(aThingy);
        }
    }
}

void doSomething(CustomThingy theThingy)
{
    try
    {
        // play with theThingy, which might result in exceptions
    }
    catch (SomeException aException)
    {
        // resolve aException somehow
    }
}

Note that I separate the "using" statement into one method and the use of the object(s) into another method with a "try"/"catch" block. I may nest several "using" statements like this for related objects (I sometimes go three or four deep in my production code).

请注意,我将“using”语句分成一个方法,并将对象的使用分成另一种方法,并使用“try”/“catch”块。我可能会为相关对象嵌套几个这样的“使用”语句(我有时会在我的生产代码中深入三到四层)。

In my Dispose()methods for these custom IDisposableclasses, I catch exceptions (but NOT errors) and log them (using Log4net). I have never encountered a situation where any of those exceptions could possibly affect my processing. The potential errors, as usual, are allowed to propagate up the call stack and typically terminate processing with an appropriate message (the error and stack trace) logged.

Dispose()这些自定义IDisposable类的方法中,我捕获异常(但不是错误)并记录它们(使用 Log4net)。我从未遇到过任何这些异常都可能影响我的处理的情况。像往常一样,允许潜在错误向上传播调用堆栈,并且通常会通过记录的适当消息(错误和堆栈跟踪)终止处理。

If I somehow encountered a situation where a significant exception could occur during Dispose(), I would redesign for that situation. Frankly, I doubt that will ever happen.

如果我以某种方式遇到在 期间可能发生重大异常的情况Dispose(),我会针对这种情况重新设计。坦率地说,我怀疑这是否会发生。

Meanwhile, the scope and cleanup advantages of "using" make it one of my most favorite C# features. By the way, I work in Java, C#, and Python as my primary languages, with lots of others thrown in here and there, and "using" is one of my most favorite language features all around because it is a practical, everyday workhorse.

同时,“使用”的范围和清理优势使其成为我最喜欢的 C# 特性之一。顺便说一句,我使用 Java、C# 和 Python 作为我的主要语言,这里和那里还有很多其他语言,“使用”是我最喜欢的语言功能之一,因为它是实用的日常工作马.

回答by Hans Passant

"Bunch of properties" and locking at the property getter and setter level looks wrong. Your locking is much too fine-grained. In most typical object usage, you'd want to make sure that you acquired a lock to access morethan one property at the same time. Your specific case might be different but I kinda doubt it.

“一堆属性”和在属性 getter 和 setter 级别上的锁定看起来是错误的。你的锁定太细粒度了。在大多数典型的对象使用中,您需要确保获得了一个锁以同时访问多个属性。您的具体情况可能有所不同,但我有点怀疑。

Anyway, acquiring the lock when you access the object instead of the property will significantly cut down on the amount of locking code you'll have to write.

无论如何,当您访问对象而不是属性时获取锁将显着减少您必须编写的锁定代码的数量。

回答by Ajaxx

While I agree with many of the above comments, including the granularity of the lock and questionable exception handling, the question is one of approach. Let me give you one big reason why I prefer using over the try {} finally model... abstraction.

虽然我同意上述许多评论,包括锁的粒度和可疑的异常处理,但问题是方法之一。让我给你一个重要的原因,为什么我更喜欢使用 try {} finally 模型...抽象。

I have a model very similar to yours with one exception. I defined a base interface ILock and in it I provided one method called Acquire(). The Acquire() method returned the IDisposable object and as a result means that as long as the object I am dealing with is of type ILock that it can be used to do a locking scope. Why is this important?

我有一个与你非常相似的模型,但有一个例外。我定义了一个基本接口 ILock,并在其中提供了一种称为 Acquire() 的方法。Acquire() 方法返回 IDisposable 对象,因此意味着只要我正在处理的对象是 ILock 类型,它就可以用于锁定范围。为什么这很重要?

We deal with many different locking mechanisms and behaviors. Your lock object may have a specific timeout that employs. Your lock implementation may be a monitor lock, reader lock, writer lock or spin lock. However, from the perspective of the caller all of that is irrelevant, what they care about is that the contract to lock the resource is honored and that the lock does it in a manner consistent with it's implementation.

我们处理许多不同的锁定机制和行为。您的锁定对象可能具有使用的特定超时。您的锁实现可能是监视器锁、读取器锁、写入器锁或自旋锁。然而,从调用者的角度来看,所有这些都无关紧要,他们关心的是锁定资源的合同是否得到遵守,并且锁定以与其实现一致的方式执行。

interface ILock {
    IDisposable Acquire();
}

class MonitorLock : ILock {
    IDisposable Acquire() { ... acquire the lock for real ... }
}

I like your model, but I'd consider hiding the lock mechanics from the caller. FWIW, I've measured the overhead of the using technique versus the try-finally and the overhead of allocating the disposable object will have between a 2-3% performance overhead.

我喜欢你的模型,但我会考虑对调用者隐藏锁定机制。FWIW,我测量了 using 技术与 try-finally 的开销,分配一次性对象的开销将有 2-3% 的性能开销。