RSCG – DisposableHelpers

RSCG – DisposableHelpers
 
 

name DisposableHelpers
nuget https://www.nuget.org/packages/DisposableHelpers/
link https://github.com/Kiryuumaru/DisposableHelpers
author Clynt Neiko Rupinta

Generating boilerplate for thread safe Dispose

 

This is how you can use DisposableHelpers .

The code that you start with is

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
<Project Sdk="Microsoft.NET.Sdk">
 
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net7.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>
 
     <PropertyGroup>
        <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
        <CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\GX</CompilerGeneratedFilesOutputPath>
    </PropertyGroup>
 
     <ItemGroup>
       <PackageReference Include="DisposableHelpers" Version="1.1.16" />
     </ItemGroup>
 
</Project>

The code that you will use is

1
2
3
4
5
6
7
using IDisposableGeneratorDemo;
using (var db = new DALDB())
{
    Console.WriteLine("before releasing");
}
Console.WriteLine("after releasing");
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
using DisposableHelpers.Attributes;
using System.Resources;
 
namespace IDisposableGeneratorDemo;
 
 
[Disposable]
partial class DALDB
{
     
    private readonly ConnectionDB cn;
    private readonly ConnectionDB cn1;
 
    public DALDB()
    {
        cn = new ConnectionDB();
        cn1=new ConnectionDB();
    }
 
    protected void Dispose(bool disposing)
    {
        if (disposing)
        {
            cn.Dispose();
            cn1.Dispose();
        }
    }
}
1
2
3
4
5
6
7
8
9
namespace IDisposableGeneratorDemo;
 
class ConnectionDB : IDisposable
{
    public void Dispose()
    {
        Console.WriteLine("disposing connectiondb");
    }
}

 

The code that is generated is

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
#pragma warning disable CS0436
#nullable enable
 
namespace Benutomo
{
    /// <summary>
    /// 指定したクラスに破棄(<see cref=""System.IDisposable"" />,<see cref=""System.IAsyncDisposable"" />)をサポートするメンバを破棄する<see cref=""System.IDisposable.Dispose"" />メソッドおよび<see cref=""System.IAsyncDisposable.DisposeAsync"" />メソッド(当該クラスに<see cref=""System.IAsyncDisposable"" />インターフェイスが含まれている場合のみ)を自動実装する。
    /// </summary>
    [global::System.AttributeUsage(global::System.AttributeTargets.Class)]
    internal class AutomaticDisposeImplAttribute : global::System.Attribute
    {
        /// <summary>
        /// 自動破棄実装の既定動作を設定する。
        /// </summary>
        public AutomaticDisposeImplMode Mode { get; set; }
    }
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
#pragma warning disable CS0436
#nullable enable
 
namespace Benutomo
{
    /// <summary>
    /// 破棄(<see cref=""System.IDisposable"" />,<see cref=""System.IAsyncDisposable"" />)をサポートするメンバを自動実装Disposeの対象とすることに関する振る舞いの指定。
    /// </summary>
    internal enum AutomaticDisposeImplMode
    {
        /// <summary>
        /// <see cref=""System.IDisposable"" />,<see cref=""System.IAsyncDisposable"" />を継承する型を持つメンバは暗黙的に自動Dispose呼び出しの対象となる。
        /// </summary>
        Implicit,
 
        /// <summary>
        /// <see cref=""System.IDisposable"" />,<see cref=""System.IAsyncDisposable"" />を継承する型を持つメンバは自動Dispose呼び出しの対象となる。
        /// </summary>
        Explicit,
    }
}
01
02
03
04
05
06
07
08
09
10
11
12
13
#pragma warning disable CS0436
#nullable enable
 
namespace Benutomo
{
    /// <summary>
    /// このメンバに対して、<see cref=""System.IDisposable.Dispose"" />メソッドまたは<see cref=""System.IAsyncDisposable.DisposeAsync"" />メソッドの自動呼出しは行いません。このオブジェクトで破棄するのが不適当であるかユーザ自身が<see cref=""System.IDisposable.Dispose"" />メソッドまたは<see cref=""System.IAsyncDisposable.DisposeAsync"" />メソッドの呼び出しを実装するメンバです。
    /// </summary>
    [global::System.AttributeUsage(global::System.AttributeTargets.Field | global::System.AttributeTargets.Property)]
    internal class DisableAutomaticDisposeAttribute : global::System.Attribute
    {
    }
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
#pragma warning disable CS0436
#nullable enable
 
namespace Benutomo
{
    /// <summary>
    /// このオブジェクトの破棄と同時に自動的に<see cref=""System.IDisposable.Dispose"" />メソッドまたは<see cref=""System.IAsyncDisposable.DisposeAsync"" />メソッドを呼び出します。
    /// </summary>
    [global::System.AttributeUsage(global::System.AttributeTargets.Field | global::System.AttributeTargets.Property)]
    internal class EnableAutomaticDisposeAttribute : global::System.Attribute
    {
        public EnableAutomaticDisposeAttribute() { }
 
        /// <summary>
        /// このオブジェクトの破棄と同時に自動的に<see cref=""System.IDisposable.Dispose"" />メソッドまたは<see cref=""System.IAsyncDisposable.DisposeAsync"" />メソッドを呼び出します。
        /// </summary>
        /// <param name=""linkedMembers"">このメンバの破棄に連動して破棄されるメンバ(ここで列挙されたメンバはEnable/DisableAutomaticDispose属性を省略可能)</param>
        public EnableAutomaticDisposeAttribute(params string[] dependencyMembers) { }
    }
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#nullable enable
#pragma warning disable CS0612,CS0618,CS0619
namespace IDisposableGeneratorDemo
{
    partial class DALDB // This is implementation class by AutomaticDisposeImpl.
    {
        [global::System.ComponentModel.Browsable(false)]
        [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
        [global::System.Obsolete("AutomaticDisposeImplによって生成されたフィールドです。一般のコードから参照してはいけません。")]
        private const int __generator_internal_BeNotInitiatedAnyDispose = 0;
        [global::System.ComponentModel.Browsable(false)]
        [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
        [global::System.Obsolete("AutomaticDisposeImplによって生成されたフィールドです。一般のコードから参照してはいけません。")]
        private const int __generator_internal_InitiatedSyncDispose  = 1;
        [global::System.ComponentModel.Browsable(false)]
        [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
        [global::System.Obsolete("AutomaticDisposeImplによって生成されたフィールドです。一般のコードから参照してはいけません。")]
        private const int __generator_internal_InitiatedAsyncDispose = 2;
        [global::System.ComponentModel.Browsable(false)]
        [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
        [global::System.Obsolete("AutomaticDisposeImplによって生成されたフィールドです。一般のコードから参照してはいけません。")]
        private const int __generator_internal_DisposeAlreadyCompleted = 9;
        [global::System.ComponentModel.Browsable(false)]
        [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
        [global::System.Obsolete("AutomaticDisposeImplによって生成されたフィールドです。一般のコードから参照してはいけません。")]
        private int __generator_internal_disposeState = __generator_internal_BeNotInitiatedAnyDispose;
 
        public bool IsDisposed => (global::System.Threading.Thread.VolatileRead(ref __generator_internal_disposeState) != __generator_internal_BeNotInitiatedAnyDispose);
 
        [global::System.ComponentModel.Browsable(false)]
        [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
        [global::System.Obsolete("AutomaticDisposeImplによって生成されたフィールドです。一般のコードから参照してはいけません。")]
        private int __generator_internal_managedObjectDisposeState = 0;
 
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                var managedObjectDisposeState = global::System.Threading.Interlocked.Exchange(ref __generator_internal_managedObjectDisposeState, 1);
                if (managedObjectDisposeState == 0)
                {
                    try
                    {
                        (this.cn as global::System.IDisposable)?.Dispose();
                    }
                    catch (global::System.Exception ex)
                    {
                        global::System.Diagnostics.Debug.Fail($"Caught an exception in the cn.Dispose() calling. Message=\"{ex.Message}\"");
                    }
                    try
                    {
                        (this.cn1 as global::System.IDisposable)?.Dispose();
                    }
                    catch (global::System.Exception ex)
                    {
                        global::System.Diagnostics.Debug.Fail($"Caught an exception in the cn1.Dispose() calling. Message=\"{ex.Message}\"");
                    }
                }
            }
        }
 
        public void Dispose()
        {
            var dispose_state = global::System.Threading.Interlocked.CompareExchange(ref __generator_internal_disposeState, __generator_internal_InitiatedSyncDispose, __generator_internal_BeNotInitiatedAnyDispose);
            if (dispose_state == __generator_internal_BeNotInitiatedAnyDispose)
            {
 
                // Dispose managed members and release unmaneged resources.
                Dispose(disposing: true);
 
                global::System.Threading.Thread.VolatileWrite(ref __generator_internal_disposeState, __generator_internal_DisposeAlreadyCompleted);
            }
        }
    }
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
#pragma warning disable CS0436
#nullable enable
 
namespace Benutomo
{
    /// <summary>
    /// <see cref=""Benutomo.AutomaticDisposeImplAttribute""/>を利用しているクラスで、ユーザが実装するマネージドオブジェクトを非同期的な処理による破棄を行うメソッドに付与する。このメソッドはデストラクタからは呼び出されない。デストラクタからも呼び出される必要がある場合はデストラクタで必要な処理を全て同期的に行うようにした上で<see cref=""Benutomo.UnmanagedResourceReleaseMethodAttribute"">を使用すること。この属性を付与するメソッドは引数なしで戻り値は<see cref=""System.Threading.ValueTask"" />などawait可能な型である必要がある。このメソッドはこのオブジェクトのDisposeAsync()が初めて実行された時に自動実装コードから呼び出される。ただし、このメソッドを所有するクラスがIDisposableも実装していて、かつ、Dispose()によってこのオブジェクトが破棄された場合は、この属性が付与されているメソッドは呼び出されず、<see cref=""Benutomo.ManagedObjectDisposeMethodAttribute"">が付与されているメソッドが呼び出される。
    /// </summary>
    [global::System.AttributeUsage(global::System.AttributeTargets.Method)]
    internal class ManagedObjectAsyncDisposeMethodAttribute : global::System.Attribute
    {
        /// <summary>
        /// <inheritdoc cref=""Benutomo.ManagedObjectAsyncDisposeMethodAttribute""/>
        /// </summary>
        public ManagedObjectAsyncDisposeMethodAttribute() { }
    }
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
#pragma warning disable CS0436
#nullable enable
 
namespace Benutomo
{
    /// <summary>
    /// <see cref=""Benutomo.AutomaticDisposeImplAttribute""/>を利用しているクラスで、ユーザが実装するマネージドオブジェクトを同期的な処理による破棄を行うメソッドに付与する。このメソッドはデストラクタからは呼び出されない。デストラクタからも呼び出される必要がある場合は<see cref=""Benutomo.UnmanagedResourceReleaseMethodAttribute"">を使用すること。この属性を付与するメソッドは引数なしで戻り値はvoidである必要がある。このメソッドはこのオブジェクトのDispose()が初めて実行された時に自動実装コードから呼び出される。ただし、このメソッドを所有するクラスがIAsyncDisposableも実装していて、かつ、DisposeAsync()によってこのオブジェクトが破棄された場合は、この属性が付与されているメソッドは呼び出されず、<see cref=""Benutomo.ManagedObjectAsyncDisposeMethodAttribute"">が付与されているメソッドが呼び出される。
    /// </summary>
    [global::System.AttributeUsage(global::System.AttributeTargets.Method)]
    internal class ManagedObjectDisposeMethodAttribute : global::System.Attribute
    {
        /// <summary>
        /// <inheritdoc cref=""Benutomo.ManagedObjectDisposeMethodAttribute""/>
        /// </summary>
        public ManagedObjectDisposeMethodAttribute() { }
    }
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
#pragma warning disable CS0436
#nullable enable
 
namespace Benutomo
{
    /// <summary>
    /// <see cref=""Benutomo.AutomaticDisposeImplAttribute""/>を利用しているクラスで、ユーザが実装するアンマネージドリソースの解放を行うメソッド(引数なしで戻り値はvoid)に付与する。このメソッドはこのオブジェクトのDispose()またはDisposeAsync()、デストラクタのいずれかが初めて実行された時に自動実装コードから呼び出される。この属性を付与したメソッドは、実装者の責任でGCのファイナライズスレッドから呼び出されても問題無いように実装しなければならないことに注意すること。
    /// </summary>
    [global::System.AttributeUsage(global::System.AttributeTargets.Method)]
    internal class UnmanagedResourceReleaseMethodAttribute : global::System.Attribute
    {
        /// <summary>
        /// <inheritdoc cref=""Benutomo.UnmanagedResourceReleaseMethodAttribute""/>
        /// </summary>
        public UnmanagedResourceReleaseMethodAttribute() { }
    }
}
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
// <auto-generated/>
#pragma warning disable
#nullable enable
namespace IDisposableGeneratorDemo
{
    partial class DALDB : global::System.IDisposable
    {
#nullable disable
        /// <summary>
        /// Finalizes an instance of the <see cref = "Disposable"/> class.
        /// </summary>
        [global::System.CodeDom.Compiler.GeneratedCode("DisposableHelpers.SourceGenerators.DisposableGenerator", "1.0.0.0")]
        [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
        ~DALDB()
        {
            Dispose(false);
        }
 
        /// <summary>
        /// Gets a value indicating whether this object is in the process of disposing.
        /// </summary>
        [global::System.CodeDom.Compiler.GeneratedCode("DisposableHelpers.SourceGenerators.DisposableGenerator", "1.0.0.0")]
        [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
        public bool IsDisposing => global::System.Threading.Interlocked.CompareExchange(ref disposeStage, DisposalStarted, DisposalStarted) == DisposalStarted;
 
        /// <summary>
        /// Gets a value indicating whether this object has been disposed.
        /// </summary>
        [global::System.CodeDom.Compiler.GeneratedCode("DisposableHelpers.SourceGenerators.DisposableGenerator", "1.0.0.0")]
        [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
        public bool IsDisposed => global::System.Threading.Interlocked.CompareExchange(ref disposeStage, DisposalComplete, DisposalComplete) == DisposalComplete;
 
        /// <summary>
        /// Gets a value indicating whether this object has been disposed or is in the process of being disposed.
        /// </summary>
        [global::System.CodeDom.Compiler.GeneratedCode("DisposableHelpers.SourceGenerators.DisposableGenerator", "1.0.0.0")]
        [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
        public bool IsDisposedOrDisposing => global::System.Threading.Interlocked.CompareExchange(ref disposeStage, DisposalNotStarted, DisposalNotStarted) != DisposalNotStarted;
 
        /// <summary>
        /// Gets the object name, for use in any <see cref = "global::System.ObjectDisposedException"/> thrown by this object.
        /// </summary>
        /// <remarks>
        /// Subclasses can override this property if they would like more control over the object name appearing in any <see cref = "global::System.ObjectDisposedException"/>
        /// thrown by this <see cref = "Disposable"/>. This can be particularly useful in debugging and diagnostic scenarios.
        /// </remarks>
        /// <value>
        /// The object name, which defaults to the class name.
        /// </value>
         
#nullable enable
        [global::System.CodeDom.Compiler.GeneratedCode("DisposableHelpers.SourceGenerators.DisposableGenerator", "1.0.0.0")]
        [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
        protected virtual string? ObjectName => GetType().FullName;
 
#nullable disable
        [global::System.CodeDom.Compiler.GeneratedCode("DisposableHelpers.SourceGenerators.DisposableGenerator", "1.0.0.0")]
        private const int DisposalNotStarted = 0;
        [global::System.CodeDom.Compiler.GeneratedCode("DisposableHelpers.SourceGenerators.DisposableGenerator", "1.0.0.0")]
        private const int DisposalStarted = 1;
        [global::System.CodeDom.Compiler.GeneratedCode("DisposableHelpers.SourceGenerators.DisposableGenerator", "1.0.0.0")]
        private const int DisposalComplete = 2;
        // see the constants defined above for valid values
        [global::System.CodeDom.Compiler.GeneratedCode("DisposableHelpers.SourceGenerators.DisposableGenerator", "1.0.0.0")]
        private int disposeStage;
        /// <summary>
        /// Occurs when this object is about to be disposed.
        /// </summary>
         
#nullable enable
        [global::System.CodeDom.Compiler.GeneratedCode("DisposableHelpers.SourceGenerators.DisposableGenerator", "1.0.0.0")]
        [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
        public event global::System.EventHandler? Disposing;
        /// <summary>
        /// Disposes of this object, if it hasn't already been disposed.
        /// </summary>
        [global::System.CodeDom.Compiler.GeneratedCode("DisposableHelpers.SourceGenerators.DisposableGenerator", "1.0.0.0")]
        [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
        public void Dispose()
        {
            if (global::System.Threading.Interlocked.CompareExchange(ref disposeStage, DisposalStarted, DisposalNotStarted) != DisposalNotStarted)
            {
                return;
            }
 
            OnDisposing();
            Disposing = null;
            Dispose(true);
            global::System.GC.SuppressFinalize(this);
            global::System.Threading.Interlocked.Exchange(ref disposeStage, DisposalComplete);
        }
 
        /// <summary>
        /// Verifies that this object is not in the process of disposing, throwing an exception if it is.
        /// </summary>
        [global::System.CodeDom.Compiler.GeneratedCode("DisposableHelpers.SourceGenerators.DisposableGenerator", "1.0.0.0")]
        [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
        protected void VerifyNotDisposing()
        {
            if (IsDisposing)
            {
                throw new global::System.ObjectDisposedException(ObjectName);
            }
        }
 
        /// <summary>
        /// Verifies that this object has not been disposed, throwing an exception if it is.
        /// </summary>
        [global::System.CodeDom.Compiler.GeneratedCode("DisposableHelpers.SourceGenerators.DisposableGenerator", "1.0.0.0")]
        [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
        protected void VerifyNotDisposed()
        {
            if (IsDisposed)
            {
                throw new global::System.ObjectDisposedException(ObjectName);
            }
        }
 
        /// <summary>
        /// Verifies that this object is not being disposed or has been disposed, throwing an exception if either of these are true.
        /// </summary>
        [global::System.CodeDom.Compiler.GeneratedCode("DisposableHelpers.SourceGenerators.DisposableGenerator", "1.0.0.0")]
        [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
        protected void VerifyNotDisposedOrDisposing()
        {
            if (IsDisposedOrDisposing)
            {
                throw new global::System.ObjectDisposedException(ObjectName);
            }
        }
 
        /// <summary>
        /// Raises the <see cref = "Disposing"/> event.
        /// </summary>
        [global::System.CodeDom.Compiler.GeneratedCode("DisposableHelpers.SourceGenerators.DisposableGenerator", "1.0.0.0")]
        [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
        protected virtual void OnDisposing()
        {
            Disposing?.Invoke(this, new global::System.EventArgs());
        }
    }
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by the Disposer source generator
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
 
namespace IDisposableGeneratorDemo
{
    partial class DALDB : global::System.IDisposable
    {
        partial void DisposeManaged();
        partial void DisposeUnmanaged();
 
        private bool disposed = false;
 
        ~DALDB()
        {
            Dispose(false);
        }
 
        private void Dispose(bool disposing)
        {
            if (disposed)
                return;
 
            if (disposing)
            {
                DisposeManaged();
            }
 
            DisposeUnmanaged();
 
            disposed = true;
        }
 
        public void Dispose()
        {
            Dispose(true);
            global::System.GC.SuppressFinalize(this);
        }
    }
}
1
2
// <autogenerated/>
namespace IDisposableGeneratorDemo;
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// <autogenerated/>
#pragma warning disable SA1636, 8618
namespace IDisposableGenerator
{
    using System;
 
    // used only by a source generator to generate Dispose() and Dispose(bool).
    [AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)]
    internal class CallOnDisposeAttribute : Attribute
    {
        public CallOnDisposeAttribute()
        {
        }
    }
 
    // used only by a source generator to generate Dispose() and Dispose(bool).
    [AttributeUsage(AttributeTargets.Event | AttributeTargets.Field | AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
    internal class DisposeFieldAttribute : Attribute
    {
        public DisposeFieldAttribute(bool owner)
        {
        }
    }
 
    // used only by a source generator to generate Dispose() and Dispose(bool).
    [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
    internal class GenerateDisposeAttribute : Attribute
    {
        public GenerateDisposeAttribute(bool stream)
        {
        }
    }
 
    // used only by a source generator to generate Dispose() and Dispose(bool).
    [AttributeUsage(AttributeTargets.Event | AttributeTargets.Field | AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
    internal class NullOnDisposeAttribute : Attribute
    {
        public NullOnDisposeAttribute()
        {
        }
    }
}
#pragma warning restore SA1636, 8618

Code and pdf at

https://ignatandrei.github.io/RSCG_Examples/v2/docs/DisposableHelpers