当我在两个相互依赖的IDisposable对象之间有循环依赖关系,并且还应该相互传播处理时,我应该如何最好地避免Dispose()方法中的无限递归产生StackOverflowException呢?

我最初的猜测是保存并判断对象是否已经在调用堆栈中调用了它的Dispose()方法,并在本例中提前退出,就像我在示例实现中所做的那样:

using CircularDispose;
using JetBrains.Annotations;

Example a = new(nameof(a));
Example b = new(nameof(b));

a.Other = b;
b.Other = a;

Console.WriteLine("Start");

a.Dispose();

Console.WriteLine($"{nameof(a)} is disposed: {a.IsDisposed}");
Console.WriteLine($"{nameof(b)} is disposed: {b.IsDisposed}");

Console.WriteLine("Finished");

namespace CircularDispose
{
    [PublicAPI]
    public class Example : IDisposable
    {
        private bool _isDisposing;
        private string _name;

        public Example? Other { get; set; }

        public bool IsDisposed { get; private set; }

        public Example(string name) => _name = name;

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

        private void Dispose(bool disposing)
        {
            Console.WriteLine($"{_name}.{nameof(Dispose)}({disposing}) was called");

            if (!_isDisposing && !IsDisposed)
            {
                _isDisposing = true;

                if (disposing)
                {
                    DisposeManagedResources();
                }

                DisposeUnmanagedResources();

                IsDisposed = true;
            }
        }

        ~Example()
        {
            Dispose(false);
        }

        [PublicAPI]
        protected virtual void DisposeUnmanagedResources()
        {
            // dispose unmanaged resources
        }

        [PublicAPI]
        protected virtual void DisposeManagedResources()
        {
            Other?.Dispose();
            Other = null;
        }
    }
}

不过,我没有在快速的谷歌搜索中找到这样的实现,所以我想知道做、保存和好是不是都可以.

程序输出为:

Start
a.Dispose(True) was called
b.Dispose(True) was called
a.Dispose(True) was called
a is disposed: True
b is disposed: True
Finished

推荐答案

正如已经在 comments 中指出的那样,解决方案并不是太糟糕.但是,它可以简化一点,不再需要额外的_isDisposing字段:

        private void Dispose(bool disposing)
        {
            Console.WriteLine($"{_name}.{nameof(Dispose)}({disposing}) was called");

            if (!IsDisposed)
            {
                IsDisposed = true;

                if (disposing)
                {
                    DisposeManagedResources();
                }

                DisposeUnmanagedResources();
            }
        }

如果内存消耗是个问题(例如,因为您有a lot个类实例),一个诀窍是重用DisposeManagedResources()部分中已经使用的现有变量,例如本例中的Other变量.如果它为空,则已经调用了Dispose(或者不是必需的).DisposeUnmanagedResources()永远不应该是循环的,因为这样您就会接触到非托管资源,因此它也可能在if之外.

Csharp相关问答推荐

在C# 11之前, struct 中的每个字段都必须显式分配?不能繁殖

PredicateBuilder不是循环工作,而是手动工作

在命令行中使用时安装,但在单击时不会安装

实体框架核心上是否支持使用NPGSQL的字符串聚合?

ITypeLib2.GetLibStatistics()在C#中总是抛出AccessViolationException

在C#中,DirectoryEntry返回空AuditRules集合,即使审计规则确实存在

Polly v8—使用PredicateBuilder重试特定的状态代码

.NET 8 Web-API返回空列表

如何将不同类型的扩展参数的javascript函数转换成C#风格?

集合表达式没有目标类型

确定System.Text.Json序列化中是否无法识别Type

有没有类似于扩展元素的合并元组的语法?

如何让两个.NET版本不兼容的项目对话?

同一组件的多个实例触发相同的事件处理程序

泛型参数在.NET 8 AOT中没有匹配的批注

在PostgreSQL上使用ExecuteSqlRawAsync的C#11原始字符串文字有区分大小写的问题

C#中类库项目的源代码生成器

为什么我可以在注册表编辑器中更改值,但不能在以管理员身份运行的C#表单应用程序中更改?

使用本地公共PEM文件加密字符串,使用Azure KayVault中的私钥解密

.NET8支持Vector512,但为什么向量不能达到512位?