与 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();
}
}
}
编辑:这个问题是为了比较 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();
}
}
}