关于 IntPtr 等有什么特殊之处,这意味着它们可以在终结器中被触摸

本文关键字:触摸 意味着 什么 IntPtr 关于 | 更新日期: 2023-09-27 18:35:36

在标准 dispose/finalise 模式(例如 C# 中使用 Dispose() 的终结器)中,如果从终结器调用该方法,则 Dispose(bool) 不会接触托管对象,这被认为是不安全的,因为它们可能已经被垃圾回收器收集。

IntPtr 等有什么特别之处使它们安全?

作为一些背景,为了使清理代码靠近分配代码,我在分配后立即将清理操作添加到事件中,然后从 dispose 方法调用该事件:

class TestClass : IDisposable
{
    private IntPtr buffer;
    public void test()
    {
        buffer = Marshal.AllocHGlobal(1024);
        OnFreeUnmanagedResource += (() => Marshal.FreeHGlobal(buffer));
    }
    private List<IDisposable> managedObjectsToBeDisposed = new List<IDisposable>();
    private event Action OnFreeUnmanagedResource = delegate { };
    private bool _isDisposed = false;
    private void Dispose(bool itIsSafeToAlsoFreeManagedObjects)
    {
        if (_isDisposed) return;
        OnFreeUnmanagedResource();
        if (itIsSafeToAlsoFreeManagedObjects) 
            for (var i = managedObjectsToBeDisposed.Count - 1; i >= 0; i--)
            {
                var managedObjectToBeDisposed = managedObjectsToBeDisposed[i];
                managedObjectToBeDisposed.Dispose();
                managedObjectsToBeDisposed.Remove(managedObjectToBeDisposed);
            }
        _isDisposed = true;
    }
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this); 
    }
    ~TestClass()
    {
        Dispose(false);
    }
}

我不确定这段代码,因为OnFreeUnmanagedResource可能会在上课之前收集,但为什么buffer不是这种情况呢?

关于 IntPtr 等有什么特殊之处,这意味着它们可以在终结器中被触摸

有了这种反模式(实际上,您最好只使用托管字段或仅具有非托管字段,而不是混合它们,然后必须聪明地处理这种混合,但唉,模式仍然存在,有时必须处理)危险不在于一次性托管对象可能已被收集(它们不会, 他们被相关类中的字段保持活力,现在至少从最终队列中的该对象扎根,如果不是其他地方),但他们可能已经由他们自己的终结者完成。或者相反,它们可能会在这里完成,然后再次完成,因为它们已经在最终确定队列中。

如果您通过Dispose()到达此代码,那么它们就不会(显然假设没有错误)已被清理,因为它们仅在可以清理它们的收集尝试之前才有路径。

如果你是通过终结器到达此代码的,那么这个对象已经尝试了收集,并被放入了终结队列中,这意味着可能只能通过它访问的对象也尝试了收集,如果可完成已被放入队列中,并且不能保证哪个被放在第一位。

如果对象是一次性的但不可完成,则它很可能依次具有可完成的字段,并且同样可能位于该队列中。

如果对象是一次性的,但不可最终确定,并且没有可最终确定的字段,那么你不对它做任何事情都没有关系。

这样就可以了:

private void Dispose(bool itIsSafeToAlsoFreeManagedObjects)
{
    if (_isDisposed) return;
    Marshal.FreeHGlobal(buffer));
    if (itIsSafeToAlsoFreeManagedObjects) 
        for (var i = managedObjectsToBeDisposed.Count - 1; i >= 0; i--)
        {
            var managedObjectToBeDisposed = managedObjectsToBeDisposed[i];
            managedObjectToBeDisposed.Dispose();
            managedObjectsToBeDisposed.Remove(managedObjectToBeDisposed);
        }
    _isDisposed = true;
}

IntPtr是一个结构,本质上包含一个本机句柄。句柄引用的资源不会被垃圾回收器触及。结构本身也仍然有效。

我不太确定您的代码,您在终结器中使用引用类型的托管对象(附加到 OnFreeUnmanagedResource 的委托)。

编辑:阅读另一个答案后,我认为您的代码也可以,因为委托没有终结器。