Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/.net/23.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
.net 在终结器中访问引用类型成员变量是否安全?_.net_Finalizer - Fatal编程技术网

.net 在终结器中访问引用类型成员变量是否安全?

.net 在终结器中访问引用类型成员变量是否安全?,.net,finalizer,.net,Finalizer,换句话说, class Foo { object obj; Foo() { obj = new object(); } ~Foo() { obj.ToString(); /* NullReferenceException? */ } } 如果这是一个问题,那么处置可能比最终确定要好。来源: 即使一个对象引用另一个对象,也不能保证两个对象的终结器以任何特定顺序运行。也就是说,如果对象A引用了对象B,并且两者都有终结器,那么当对象A的终结器启动时,对象B可能已经终结 简而言

换句话说,

class Foo
{
    object obj;
    Foo() { obj = new object(); }
    ~Foo() { obj.ToString(); /* NullReferenceException? */ }
}

如果这是一个问题,那么处置可能比最终确定要好。

来源:

即使一个对象引用另一个对象,也不能保证两个对象的终结器以任何特定顺序运行。也就是说,如果对象A引用了对象B,并且两者都有终结器,那么当对象A的终结器启动时,对象B可能已经终结

简而言之,在终结器期间,不能对引用对象的状态进行任何假设

在几乎所有情况下,终结器中实现的逻辑都属于一次性模式。这是一个如何使用接口在.NET中正确实现模式的示例

public class MyClass : IDisposable
{
    private bool _disposed;

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!_disposed)
        {
            if(disposing)
            {
                // Release unmanaged resources.
            }

            // Release managed resources (Streams, SqlConnections, etc.)
        }

        _disposed = true;
    }
}
如果您使用的是非托管资源,请参阅本文,了解如何使用终结器实现
IDisposable


这不安全,因为obj可能已经被垃圾收集。还要注意,垃圾收集器不会将引用设置为null。所以即使是检查obj!=空值对您没有帮助

详情请参见此处:

“概括这一原则,在Dispose方法中,清理对象持有的所有资源是安全的,无论它们是托管对象还是本机资源但是,在终结器中,清理不可终结的对象是安全的,通常终结器应该只释放本机资源。(您的obj是可终结的,因此您不应该在其他终结器中接触它)

这也是为什么你有

如果(处理){…}


在IDisposable模式中(请参见上面链接中的图2)。

通常,您不希望在对象上实现终结器。如果需要执行托管对象的资源清理,您希望在
Dispose
中执行,并正确实现Dispose模式


如果最终实现了终结器,则只希望访问非托管资源。

当对象注册以进行终结时,它将被放置在终结队列中。当垃圾收集器运行时,所有对象都分为三类:

  • 具有根实时引用而不是终结队列的那些。
  • 唯一根引用是终结队列的那些。
  • 不存在根引用的那些。 第三种类型的对象将不再存在,尽管没有人会注意到。第一种类型的对象被认为是“活动的”。中间类型的对象(包括带有终结器的对象,以及可终结对象引用的其他对象)将有终结器(如果有的话)运行;完成终结器后,除非终结器将根引用存储到某个位置或重新注册要终结的对象,否则这些对象将不再具有任何根引用,并且将有资格进行下一次垃圾收集。

    在终结过程中使用对其他对象的引用的唯一真正危险是,除非对象具有适当的联锁,否则在终结器尝试清理它们时,无法知道它们可能处于什么状态;它们甚至可能正在使用中。使用Threading.Interlocked.Exchange来测试和删除它们可能是个好主意t一个标志,指示正在进行清理。

    顺便说一句,微软在其文档中没有强调以下几点:

  • 因为可终结对象持有直接或间接引用的任何对象都不适合进行垃圾收集,所以带有终结器的对象不应该持有对终结不需要的任何对象的引用。
  • 尽管Microsoft的Dispose模式试图帮助清理具有托管和非托管资源的对象(我认为更好的术语是自清理和非自清理),但这些对象几乎总是会将资源保存到终结期间不需要的对象上(与规则#1相冲突)。
    更好的做法是,IMHO采用以下原则:非自清理资源应该放在它们自己的类中,这些类的唯一目的是处理它们的清理并公开对象以供其他地方使用;这些类应该是唯一具有终结器的类。只有从对象派生的类才应该实现终结器;其他派生类添加非自清理资源的类应该将这些资源封装到单独的自清理类中,然后保存对它们的引用。

    虽然这不是对您问题的回答,但我编写终结器和dispose方法,就像编写C代码一样,在执行之前先检查。
    if(obj!=null)obj.dispose()
    这只是因为在这些方法中跟踪和处理异常非常痛苦。你真的需要终结器吗?大多数C#类不应该有终结器。仅仅因为你实现了
    IDisposable
    并不意味着你应该实现终结器。事实上,你几乎从来都不想实现终结器。然而,如果你真的实现了终结器删除终结器您还应该实现
    IDisposable
    。只有当您的对象直接包含非托管资源时,您才需要终结器。您还应该将非托管资源句柄包装到类似
    SafeFileHandle
    的对象中,以便生成的终结器不会对GC造成过度负载。我同意您不想这样做除非您正在处理非托管资源,否则请实现终结器。我本来希望重载
    Dispose
    可以清楚地说明这一点,但我将重新编写代码示例,以便仅处理托管资源场景。仅针对你们;)事实上,如果您刚刚删除终结器,您的原始代码示例是正确的。为了正确地实现Dis在pose模式中,您确实需要一个
    公共虚拟void Dispose(bool disposing)
    方法,该方法包含您的所有处置逻辑(正如您在原始代码中所做的那样),并且
    IDisposable.Dispose()
    方法实现只需调用它即可