【发布时间】:2017-06-22 04:27:13
【问题描述】:
编辑:这个问题旨在比较 C# 和 C++ 实现。讨论时无需戏剧化。无论如何,我个人更喜欢用 C# 开发。
假设我有以下课程:
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 对象。
这回答了一个问题,即当对象超出范围时,MS 使 GC 以类似方式工作有多“难”。它只是自动调用GC.Collect(),这会强制 GC 调用析构函数,类似于 C++ 中发生的情况。
【问题讨论】:
-
如果你想知道他们为什么做某事,你需要问微软他们为什么做某事。你在这里得到的只是意见。我会说,将 C# 析构函数视为 C++ 析构函数是完全错误的。它们完全不同。 C# 没有堆栈分配的类,因此“超出范围”的想法不适用。唯一重要的是根据 GC 的规则,堆分配的对象是否可以访问。
-
不同的语言,不同的行为。垃圾收集器的非确定性行为(你不知道它什么时候会运行,只是它会运行)在 RAII 上扮演着邪恶的地狱,所以如果你想在给定时间释放资源,你该死的必须做它自己而不是指望析构函数。从好的方面来说,大多数内存管理都是一劳永逸的。权衡。
-
您可能想阅读 Eric Lippert 的关于 C# 中终结器的博文ericlippert.com/2015/05/18/…
-
还有Raymond Chen's piece on garbage collectors。本来想写答案的,为什么要这么麻烦?陈先生已经做到了。
-
“对于 MS 来说,实现类似的析构函数会有多复杂”——本身并不复杂,但与他们的整个设计目标背道而驰。 “我可以实现在超出范围时自动调用的析构函数”——关于学习从真正的 RAII 语言到类似 C# 语言所需的习语,已经有广泛的讨论。 C# 终结器是处理错误代码的备份;您的代码设计首先不应该依赖它们,因此它们何时执行对您来说应该无关紧要。正确编写您的代码,这并不重要。
标签: c# c++ garbage-collection c++-cli destructor