如何做 C++ C# 中的样式析构函数?

发布于 2024-07-05 00:31:53 字数 959 浏览 11 评论 0原文

我通过 IDisposable 获得了一个带有 Dispose 函数的 C# 类。 它旨在在 using 块内使用,以便可以立即释放它处理的昂贵资源。

问题在于,在调用 Dispose 之前抛出异常,并且程序员忽略使用 usingfinally 时,就会出现错误。

在 C++ 中,我从来不用担心这个。 对类的析构函数的调用将自动插入到对象作用域的末尾。 避免这种情况发生的唯一方法是使用 new 运算符并将对象保存在指针后面,但这对程序员来说需要额外的工作,这并不是他们偶然会做的事情,比如忘记使用 using.

有什么方法可以在 C# 中自动使用 using 块吗?

非常感谢。

更新:

我想解释一下为什么我不接受终结器的答案。 这些答案本身在技术上是正确的,但它们不是 C++ 风格的析构函数。

这是我发现的错误,简化为要点...

try
{
    PleaseDisposeMe a = new PleaseDisposeMe();
    throw new Exception();
    a.Dispose();
}
catch (Exception ex)
{
    Log(ex);
}

// This next call will throw a time-out exception unless the GC
// runs a.Dispose in time.
PleaseDisposeMe b = new PleaseDisposeMe();

使用 FXCop 是一个很好的建议,但如果这是我唯一的答案,我的问题将不得不成为对 C# 人员的恳求,或者使用 C++ 。 有人知道二十个嵌套的 using 语句吗?

I've got a C# class with a Dispose function via IDisposable. It's intended to be used inside a using block so the expensive resource it handles can be released right away.

The problem is that a bug occurred when an exception was thrown before Dispose was called, and the programmer neglected to use using or finally.

In C++, I never had to worry about this. The call to a class's destructor would be automatically inserted at the end of the object's scope. The only way to avoid that happening would be to use the new operator and hold the object behind a pointer, but that required extra work for the programmer isn't something they would do by accident, like forgetting to use using.

Is there any way to for a using block to be automatically used in C#?

Many thanks.

UPDATE:

I'd like to explain why I'm not accepting the finalizer answers. Those answers are technically correct in themselves, but they are not C++ style destructors.

Here's the bug I found, reduced to the essentials...

try
{
    PleaseDisposeMe a = new PleaseDisposeMe();
    throw new Exception();
    a.Dispose();
}
catch (Exception ex)
{
    Log(ex);
}

// This next call will throw a time-out exception unless the GC
// runs a.Dispose in time.
PleaseDisposeMe b = new PleaseDisposeMe();

Using FXCop is an excellent suggestion, but if that's my only answer, my question would have to become a plea to the C# people, or use C++. Twenty nested using statements anyone?

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

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

发布评论

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

评论(7

风渺 2024-07-12 00:31:53

最佳实践是在类中使用终结器并始终使用 using 块。

虽然没有真正的直接等价物,终结器看起来像 C 析构函数,但行为不同。

您应该嵌套 using 块,这就是为什么 C# 代码布局默认将它们放在同一行...

using (SqlConnection con = new SqlConnection("DB con str") )
using (SqlCommand com = new SqlCommand( con, "sql query") )
{
    //now code is indented one level
    //technically we're nested twice
}

当您不使用 using 时,您可以无论如何,做它在幕后所做的事情:

PleaseDisposeMe a;
try
{
    a = new PleaseDisposeMe();
    throw new Exception();
}
catch (Exception ex) { Log(ex); }  
finally {    
    //this always executes, even with the exception
    a.Dispose(); 
}

使用托管代码,C# 非常非常擅长管理自己的内存,即使东西处理不当也是如此。 如果您经常处理非托管资源,那么它就不那么强大了。

The best practice is to use a finaliser in your class and always use using blocks.

There isn't really a direct equivalent though, finalisers look like C destructors, but behave differently.

You're supposed to nest using blocks, that's why the C# code layout defaults to putting them on the same line...

using (SqlConnection con = new SqlConnection("DB con str") )
using (SqlCommand com = new SqlCommand( con, "sql query") )
{
    //now code is indented one level
    //technically we're nested twice
}

When you're not using using you can just do what it does under the hood anyway:

PleaseDisposeMe a;
try
{
    a = new PleaseDisposeMe();
    throw new Exception();
}
catch (Exception ex) { Log(ex); }  
finally {    
    //this always executes, even with the exception
    a.Dispose(); 
}

With managed code C# is very very good at looking after its own memory, even when stuff is poorly disposed. If you're dealing with unmanaged resources a lot it's not so strong.

一枫情书 2024-07-12 00:31:53

这与程序员忘记在 C++ 中使用删除没有什么不同,只不过至少在这里垃圾收集器最终仍会赶上它。

如果您唯一担心的资源是内存,那么您永远不需要使用 IDisposable。 该框架将自行处理该问题。 IDisposable 仅适用于非托管资源,例如数据库连接、文件流、套接字等。

This is no different from a programmer forgetting to use delete in C++, except that at least here the garbage collector will still eventually catch up with it.

And you never need to use IDisposable if the only resource you're worried about is memory. The framework will handle that on it's own. IDisposable is only for unmanaged resources like database connections, filestreams, sockets, and the like.

陌伤ぢ 2024-07-12 00:31:53

更好的设计是让此类在处置之前自行释放昂贵的资源。

例如,如果它是数据库连接,则仅在需要时连接并立即释放,远远早于实际的类被释放。

A better design is to make this class release the expensive resource on its own, before its disposed.

For example, If its a database connection, only connect when needed and release immediately, long before the actual class gets disposed.

横笛休吹塞上声 2024-07-12 00:31:53
~ClassName()
{
}

编辑(粗体):

当对象移出范围并由垃圾收集器整理时,If 将被调用但是这不是确定性的,并且不能保证在任何特定时间发生
这称为终结器。 所有具有终结器的对象都会被垃圾收集器放入一个特殊的终结队列,并在其中调用它们的终结方法(因此从技术上讲,声明空终结器会影响性能)。

根据框架指南,对于非托管资源,“接受的”处置模式如下:

    public class DisposableFinalisableClass : IDisposable
    {
        ~DisposableFinalisableClass()
        {
            Dispose(false);
        }

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

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                // tidy managed resources
            }

            // tidy unmanaged resources
        }
    }

因此,上面的意思是,如果有人调用 Dispose,非托管资源就会被整理。 然而,如果有人忘记调用 Dispose 或发生阻止调用 Dispose 的异常,非托管资源仍然会被清理掉,只是稍后 GC 开始清理(包括应用程序关闭或意外结束) )。

~ClassName()
{
}

EDIT (bold):

If will get called when the object is moved out of scope and is tidied by the garbage collector however this is not deterministic and is not guaranteed to happen at any particular time.
This is called a Finalizer. All objects with a finaliser get put on a special finalise queue by the garbage collector where the finalise method is invoked on them (so it's technically a performance hit to declare empty finalisers).

The "accepted" dispose pattern as per the Framework Guidelines is as follows with unmanaged resources:

    public class DisposableFinalisableClass : IDisposable
    {
        ~DisposableFinalisableClass()
        {
            Dispose(false);
        }

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

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                // tidy managed resources
            }

            // tidy unmanaged resources
        }
    }

So the above means that if someone calls Dispose the unmanaged resources are tidied. However in the case of someone forgetting to call Dispose or an exception preventing Dispose from being called the unmanaged resources will still be tidied away, only slightly later on when the GC gets its grubby mitts on it (which includes the application closing down or unexpectedly ending).

再见回来 2024-07-12 00:31:53

在我工作的地方,我们使用以下准则:

  • 每个 IDisposable 类必须有一个终结器
  • 每当使用 IDisposable 对象时,它必须在“using”块内使用。 唯一的例外是,如果该对象是另一个类的成员,在这种情况下,包含类必须是 IDisposable,并且必须在其自己的“Dispose”实现中调用该成员的“Dispose”方法。 这意味着开发人员永远不应该调用“Dispose”,除非在另一个“Dispose”方法内,从而消除了问题中描述的错误。
  • 每个终结器中的代码必须以警告/错误日志开头,通知我们终结器已被调用。 这样,您就有很大的机会在发布代码之前发现如上所述的错误,而且它可能是系统中发生错误的提示。

为了让我们的生活更轻松,我们的基础设施中还有一个 SafeDispose 方法,它在 try-catch 块中调用其参数的 Dispose 方法(带有错误日志记录),以防万一(尽管 Dispose 方法不应该抛出异常)。

的建议

另请参阅:Chris Lyon 关于 IDisposable Edit :
@Quarrelsome:你应该做的一件事是在“Dispose”中调用 GC.SuppressFinalize,这样如果对象被处置,它就不会被“重新处置”。

通常还建议保留一个标志来指示该对象是否已被处置。 以下模式通常非常好:

class MyDisposable: IDisposable {
    public void Dispose() {
        lock(this) {
            if (disposed) {
                return;
            }

            disposed = true;
        }

        GC.SuppressFinalize(this);

        // Do actual disposing here ...
    }

    private bool disposed = false;
}

当然,锁定并不总是必要的,但如果您不确定您的类是否会在多线程环境中使用,建议保留它。

Where I work we use the following guidelines:

  • Each IDisposable class must have a finalizer
  • Whenever using an IDisposable object, it must be used inside a "using" block. The only exception is if the object is a member of another class, in which case the containing class must be IDisposable and must call the member's 'Dispose' method in its own implementation of 'Dispose'. This means 'Dispose' should never be called by the developer except for inside another 'Dispose' method, eliminating the bug described in the question.
  • The code in each Finalizer must begin with a warning/error log notifying us that the finalizer has been called. This way you have an extremely good chance of spotting such bugs as described above before releasing the code, plus it might be a hint for bugs occuring in your system.

To make our lives easier, we also have a SafeDispose method in our infrastructure, which calls the the Dispose method of its argument within a try-catch block (with error logging), just in case (although Dispose methods are not supposed to throw exceptions).

See also: Chris Lyon's suggestions regarding IDisposable

Edit:
@Quarrelsome: One thing you ought to do is call GC.SuppressFinalize inside 'Dispose', so that if the object was disposed, it wouldn't be "re-disposed".

It is also usually advisable to hold a flag indicating whether the object has already been disposed or not. The follwoing pattern is usually pretty good:

class MyDisposable: IDisposable {
    public void Dispose() {
        lock(this) {
            if (disposed) {
                return;
            }

            disposed = true;
        }

        GC.SuppressFinalize(this);

        // Do actual disposing here ...
    }

    private bool disposed = false;
}

Of course, locking is not always necessary, but if you're not sure if your class would be used in a multi-threaded environment or not, it is advisable to keep it.

醉殇 2024-07-12 00:31:53

不幸的是,没有任何方法可以直接在代码中执行此操作。 如果这是内部问题,有各种代码分析解决方案可以捕获此类问题。 你调查过FxCop吗? 我认为这将捕获这些情况以及 IDisposable 对象可能挂起的所有情况。 如果它是人们在组织外部使用的组件,并且您不需要 FxCop,那么文档实际上是您唯一的资源:)。

编辑:对于终结器来说,这并不能真正保证终结何时发生。 所以这可能是您的解决方案,但这取决于具体情况。

Unfortunately there isn't any way to do this directly in the code. If this is an issue in house, there are various code analysis solutions that could catch these sort of problems. Have you looked into FxCop? I think that this will catch these situations and in all cases where IDisposable objects might be left hanging. If it is a component that people are using outside of your organization and you can't require FxCop, then documentation is really your only recourse :).

Edit: In the case of finalizers, this doesn't really guarantee when the finalization will happen. So this may be a solution for you but it depends on the situation.

笨死的猪 2024-07-12 00:31:53

@吵架

当对象移出范围并被垃圾收集器整理时,If 将被调用。

这个声明具有误导性,我的理解也不正确:绝对不能保证何时调用终结器。 你说 billpg 应该实现终结器是完全正确的; 然而,当对象超出他想要的范围时,它不会被自动调用。 证据,第一个要点 Finalize 操作有以下限制

事实上,Microsoft 向 Chris Sells 提供了一笔资助,以创建使用引用计数而不是垃圾收集的 .NET 实现 链接。 事实证明,性能受到相当大的影响。

@Quarrelsome

If will get called when the object is moved out of scope and is tidied by the garbage collector.

This statement is misleading and how I read it incorrect: There is absolutely no guarantee when the finalizer will be called. You are absolutely correct that billpg should implement a finalizer; however it will not be called automaticly when the object goes out of scope like he wants. Evidence, the first bullet point under Finalize operations have the following limitations.

In fact Microsoft gave a grant to Chris Sells to create an implementation of .NET that used reference counting instead of garbage collection Link. As it turned out there was a considerable performance hit.

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