Question
Dispose(bool) for a managed wrapper, is it native or managed in the end?
In the dispose pattern documentation, this is suggested on how to dispose object:
protected virtual void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
if (disposing)
{
// TODO: dispose managed state (managed objects).
}
// TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
// TODO: set large fields to null.
_disposed = true;
}
Now, suppose I have a managed wrapper that allocates aligned unmanaged memory:
using System.Runtime.InteropServices;
using Whatever.Extensions;
namespace XYZ.Extensions;
public sealed class NativeMemory<T> : DisposableAsync where T : unmanaged
{
public unsafe NativeMemory(uint count, uint alignment = 1)
{
ArgumentOutOfRangeException.ThrowIfZero(alignment);
Length = count * (uint)sizeof(T);
Pointer = (nint)NativeMemory.AlignedAlloc(Length, alignment);
Manager = new NativeMemoryManager<T>((T*)Pointer, (int)Length);
Manager.Memory.Span.Clear();
}
public NativeMemoryManager<T> Manager { get; }
public uint Length { get; }
public nint Pointer { get; }
protected override ValueTask DisposeAsyncCore()
{
DisposeNative();
return ValueTask.CompletedTask;
}
protected override void DisposeNative()
{
DisposePointer();
}
private unsafe void DisposePointer()
{
NativeMemory.AlignedFree(Pointer.ToPointer());
}
}
Memory manager:
using System.Buffers;
namespace ISO9660.Extensions;
public sealed unsafe class NativeMemoryManager<T>(T* pointer, int length)
: MemoryManager<T> where T : unmanaged
{
private T* Pointer { get; } = pointer;
private int Length { get; } = length;
protected override void Dispose(bool disposing)
{
// NOP
}
public override Span<T> GetSpan()
{
return new Span<T>(Pointer, Length);
}
public override MemoryHandle Pin(int elementIndex = 0)
{
ArgumentOutOfRangeException.ThrowIfNegative(elementIndex);
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(elementIndex, Length);
return new MemoryHandle(Pointer + elementIndex);
}
public override void Unpin()
{
// NOP
}
}
Disposable helper:
using System;
#nullable disable
namespace Whatever.Extensions
{
public abstract class Disposable : IDisposable
{
protected bool IsDisposed { get; set; }
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize((object) this);
}
protected virtual void Dispose(bool disposing)
{
if (this.IsDisposed)
return;
this.DisposeNative();
if (disposing)
this.DisposeManaged();
this.IsDisposed = true;
}
protected virtual void DisposeManaged() { }
protected virtual void DisposeNative() { }
~Disposable() => this.Dispose(false);
}
}
DisposableAsync helper:
using System;
using System.Threading.Tasks;
#nullable enable
namespace Whatever.Extensions
{
public abstract class DisposableAsync : Disposable, IAsyncDisposable
{
public async ValueTask DisposeAsync()
{
DisposableAsync disposableAsync = this;
await disposableAsync.DisposeAsyncCore().ConfigureAwait(false);
GC.SuppressFinalize((object) disposableAsync);
}
protected virtual async ValueTask DisposeAsyncCore()
{
await new ValueTask().ConfigureAwait(false);
}
}
}
Now things are ambiguous... Is that a managed or unmanaged thing to dispose of?
Question:
When disposing NativeMemory<T>
, should I consider it as managed or unmanaged?