简单的析构函数问题(IDisposable接口)

4

我是C#的初学者,我不知道为什么这个方法不起作用,我只想通过调用Dispose()方法将该对象设置为null。

为什么这不可能呢?

class MyClass:IDisposable
{

    public void Dispose()
    {
        this = null;
    }

}

3
如果您在某个地方读过将this设置为null的内容,请告诉我们您读到的地方。我们应该避免使用您提供的来源。 - John Saunders
@JeppeStigNielsen “你只能在结构体内部进行赋值”。天啊,你可以这样做。但是为什么呢? - sircodesalot
@sircodesalot 这并不是经常做的事情,因为可变结构体被认为是“邪恶”的。但是还是试一下吧。编写一个结构体,在其中编写一个非静态构造函数或方法。然后编写this = x;,其中x是您结构体的另一个值(x可能是一个参数)。您可以将this视为一种ref参数(甚至在结构体的实例构造函数的情况下,它也可以是一个out参数)。 - Jeppe Stig Nielsen
@JeppeStigNielsen 哦,我试过了,所以我知道它是有效的。但是我就是无法理解它的实际应用是什么。 - sircodesalot
@sircodesalot 我不知道。也许像 struct Triple { public int X; public int Y; public int Z; public Triple(int x, int y, int z) { Triple t; t.X = x; t.Y = y; t.Z = z; this = t; } public void Rotate() { this = new Triple(Y, Z, X); } } 这样编码是一种可能的做法,如果你真的想要一个可变的值类型。当然实例构造函数有点愚蠢,因为你可以直接赋值 thisXYZ,就像在实例构造函数中通常做的那样。但请注意,t 可以以“逐步”的方式赋值。但在方法内部,也许我找到了一个好用途? - Jeppe Stig Nielsen
显示剩余3条评论
6个回答

9
< p > Dispose方法的目的不是为了清理类,而是为了清理该类所持有的可处理对象,以便可以通过垃圾回收器正常地进行处理。

我建议您进一步阅读关于Dispose模式和如何在C#中实现它的内容。

有点吹毛求疵: Dispose方法不是析构函数,也不是终结器。


1
我想说这句话,但是我找不到合适的词语... +1 - Rob van der Veer

5
您的问题似乎归结为“如何在C#中删除内容”。简短的答案是您不能这样做,这是垃圾回收器System.GC的工作。IDisposable接口用于确保重要资源(不属于.Net的资源,例如文件或网络/数据库连接,但不包括.Net类)可以根据需要进行清理。
IDisposable接口非常重要,因为它允许您使用using模式。也就是说,如果您的类实现了IDisposable接口,则可以在using块中使用它。例如考虑以下情况:
class MyClass : IDisposable {
    public void Dispose() { }
}

这个类现在可以像这样使用:
using (MyClass instance = new MyClass())
{

} // instance.Dispose() is called here at the end of the block.

使用块的重点在于当块结束时,编译器会添加一个调用Dispose()的语句,您可以利用它来处理重要的资源,例如文件和数据库连接。例如,所有Stream类(如FileStream等)都实现了IDisposable,因为保留文件打开状态是不好的做法。相反,您可以将所有访问包装在using块中,然后您可以确保FileStream.Dispose会关闭文件。请考虑以下示例:
using (FileStream myFile = File.OpenRead("...")) 
{
    // Read the content of the file.

} // The file is guaranteed to be closed here. Cool!

这比像这样做更整洁:

FileStream stream = File.OpenRead(" ... ");

stream.Close(); // Yes, you closed it manually, but it's error prone. What if you forget to do this?

现在你思考的是一个名为“Finalization”的术语,即当类实际被销毁时。这发生在垃圾回收器(System.GC类)实际销毁对象并清理其内存时。考虑以下内容:
public class MyClass {

    // This method, the 'Finalizer' will be called when the class is destroyed.
    // The 'finalizer' is essentially just the name of the class with a '~' in front.
    ~MyClass() {
        Console.WriteLine("Destroyed!");
    }
}

public class Program {
    public static void Main() {
        MyClass referenceHeld = new MyClass(); // Reference held
        new MyClass(); // No reference held on this class
        WeakReference sameAsNoReference = new WeakReference(new MyClass()); // Equivalent to no reference.

        System.GC.Collect(); // Force the garbage collector to collect
        Console.ReadLine();
    }
}

简而言之,垃圾回收器是运行时清理不再使用的内容的部分。什么意思是“不再使用”?这意味着对象上没有附加引用。例如,如果您运行上面的程序,您会注意到屏幕上打印了两次“Destroyed”。这是因为在Main函数中创建的MyClass实例中有两个未被引用指向(WeakReference本质上与没有引用相同)。当我们调用GC.Collect()时,垃圾回收器运行并清理引用。
话虽如此,你不应该自己调用GC.Collect。当然,你可以进行试验和教育,但大多数人会告诉你,垃圾回收器自己很好地保持了清洁。在你的代码中散布大量的GC.Collect没有意义,因为这就是拥有垃圾回收器的全部意义 - 不必担心自己清理东西。
所以简而言之,你真的不能自己销毁对象,除非你调用GC.Collect()(你不应该这样做)。IDisposable接口允许您使用using模式,确保重要资源被释放(这与销毁对象不同!所有IDisposable所做的就是确保在using块退出时调用Dispose(),以便您可以清理重要的东西,但对象仍然存活 - 这是一个重要的区别)。

据我所知,使用模式是一种保障(方便!),以确保在给定范围内调用IDisposable。实现它的类将检查连接是否关闭,并为您调用Close()。using等同于try/finally块,以确保调用Close()。如果您不使用dispose或using,则仍需编写finalizer来调用dispose。 - Rob van der Veer
另外:Dispose() 对性能的影响比让 GC 调用 Finalize() 函数更好。 - Recipe
@Recipe,在C#中没有确定性的finalize,这也是实现“IDisposable”的原因。 - Rob van der Veer
注意: 如果您在.NET中编写终结器,而且您不是.NET终结器专家(这与C ++中的析构函数非常不同),那么您可能做错了某些事情。这包括使用从互联网上复制/粘贴的代码(例如,许多 IDisposable 的示例包括终结器,但实际上不应该)。 - Sam Harwell

1

一个类无法将自己设置为 null,因为它无法控制谁引用它。 例如,如果您在代码中有一个带有对象的变量,该对象无法在您的代码中将自己设置为 null,它只能将其自己的成员设置为 null。

此外,想象一种情况,多个类引用同一个对象,那么它在哪里设置为 null? 这是包含该对象的类应该做的事情。


1
简单的答案是,“this”关键字是只读的,不能设置。
更深入的答案是,在c#中你不能将对象本身设置为null,但是你可以将指向对象的引用设置为null。当你将对象的引用设置为null且没有其他引用指向该对象时,对象就处于可被垃圾回收的状态。(这只是实际情况的一个简化,详见链接。)
例如:
Object oTest = new Object;
oTest = null;

在上面的示例中,即使其引用oTest已被设置为null,对象仍然存在。它只是在等待垃圾收集器来删除它。
因此,在您的代码中,看起来像您正在尝试将所有对对象的引用都设置为null,即使这可能不是您的意图。这不能从对象本身完成。您需要确保手动将对对象的所有引用设置为null,或者它们保证离开程序中的范围null (C#参考) this (C#参考)

0

Dispose模式仅在使用非CLR资源(如图形上下文或低级io)时才需要。有一些边缘情况需要立即释放资源,但是正如你所说,作为初学者,你真的不应该费心(至少现在不用)。

this设置为nil并没有帮助。考虑这个:

MyClass sample = new MyClass();
sample.Dispose();
// at this point, sample still has a value

当你想要在C#中摆脱一个对象时,你只需要让所有引用超出范围,或将它们设置为nil。(多个变量可以引用同一个实例)。运行时会自动释放对象(及其子对象),因为没有人再使用它了。

粗略地说,你可以把它们看作指针(技术上它们不是,但我们在这里试图解释原理)


1
如果您持有实现了 IDisposable 接口的其他类,那么实现 IDisposable 接口也是一个好主意。例如,如果您将流放入类的私有字段中,则该类应该实现 IDisposable 接口,以便可以在流上调用 Dispose() 方法。 - Daniel Mann

0

你不能修改自己的 this 指针。在你的示例中,你的 Dispose 方法不需要进行任何操作,因此可以完全省略它(以及更新类以不再实现 IDisposable


那么我该如何将这个对象设置为null?它必须在MyClass之外吗? - Jorge Tovar
@JorgeTovar 不需要将对象设置为 null - Daniel Mann
你可能想要了解一下垃圾回收 - 简而言之,只有在你拥有IDisposable成员或引用需要释放的本地资源时,才应该实现IDisposable。 - Rowland Shaw
@JorgeTovar 当对象超出范围或对象所有者将其设置为null时,该对象将被设置为null。请记住,这意味着对象本身不再引用该实例。如果该实例没有其他链接的引用,则会被垃圾回收器拾取。 - Recipe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接