与 C++ 析构函数相比,C# 析构函数和 GC 并没有真正解决问题

C# destructors and GC not really solving problems compared to C++ destructors

编辑:这个问题是为了比较 C# 和 C++ 实现。讨论时无需戏剧化。无论如何,我个人更喜欢用 C# 进行开发。

假设我有以下 class:

class Foo {
            FileStream f;
            public Foo() {
                f = File.Open("somefile.txt", FileMode.Open);
            }
            ~Foo() {
                f.Close();
            }
        }

然后我在 Main 中调用以下内容:

static void Main(){
    doSomething();
    // this one creates an exception, file in use
    doSomething();
}
static void doSomething(){
    Foo f = new Foo();
}

在 C++ 中,这段代码可以正常工作:

    class Foo {
public:
    std::ofstream ofs;
    Foo() { ofs = ofstream("somefile.txt"); }
    ~Foo() { ofs.close(); }
};

void doSomething() {
    Foo f();
}

int main() {
    doSomething();
    doSomething();
    return 0;
}

当第一个 doSomething() 超出范围时,第一个对象调用其析构函数并关闭文件。

在 C# 中,这将始终引发异常,指出该文件正被另一个进程使用。显然析构函数还没有被调用

参考有关析构函数的 MSDN 页面 here,它会使阅读它的人感到困惑,并使他们认为这实际上是一个析构函数。虽然它实际上是 GC 在 GC 决定调用它时调用的终结器,但这是我无法控制的。

是的,我可以实现 IDisposeable 但我不希望每个使用我的库的程序员都记得调用 Dispose() 否则他将有未处理的异常。

我可以在两个 doSomething() 方法之间调用 GC.Collect() 并且不会抛出任何异常。再一次,这将类似于 Dispose() 并且图书馆的用户将不得不记住它。更不用说 MS 告诉人们请不要使用 GC.Collect() 的无数警告!

所以我的问题是,如果 MS 想要像 C++ 那样拥有析构函数,他们是否必须只在每次方法超出范围时调用垃圾收集器?或者对他们来说会更复杂吗?这样的设计会有什么样的并发症?

此外,如果它很简单,他们为什么不这样做呢?

此外,有没有办法在超出范围时强制调用 C# 中的析构函数?

编辑:

我正在查看 C++/CLI 实现,有趣的是,关键字 gcnew 正好允许我为 C# 发生的事情。所以答案就在那个实现中。在他们的文档中,当托管对象超出范围时,将调用 GC,因此调用托管对象的析构函数。这不会强制用户手动 Dispose 对象。

这回答了有关 "hard" 当对象超出范围时 MS 如何使 GC 以类似方式工作的问题。它只是自动调用 GC.Collect() 强制 GC 调用类似于 C++ 中发生的析构函数。

如您所料,C# 没有确定性析构函数,将其与 C++ 进行比较总是 "apples to oranges"。此外,在 C# class 上实现析构函数将导致该 class 实例的 GC 清理更加不确定,因为 GC 会将任何具有析构函数的实例放入列表中,由单独的处理稍后讨论。

如果您想要确定性地处置实例,请实施 IDisposable interface/pattern。这就是它的用途。

这不是 C# 问题。您的 class 应该正确处理打开和关闭。

public static class Foo
{
    public static void DoSomething()
    {
        string someText = "someText";
        using (StreamWriter writer = new StreamWriter("myfile.txt"))
        {
            writer.Write(someText);
            writer.Close();
            writer.Dispose();
        }
    }
}