В документации по шаблону предлагается, как удалить объект:
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;
}
Теперь предположим, что у меня есть управляемая оболочка, которая выделяет выровненную неуправляемую память:
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());
}
}
Менеджер памяти:
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
}
}
Одноразовый помощник:
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:
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);
}
}
}
Теперь ситуация неоднозначная... Это управляемая или неуправляемая вещь, от которой нужно избавляться?
Вопрос:
При удалении NativeMemory<T>
следует ли считать его управляемым или неуправляемым?
Вы спрашиваете, что делать, когда вы ссылаетесь на объект NativeMemory<T>
и хотите его удалить, или вы говорите о том, что находитесь внутри NativeMemory<T>
и хотите удалить любые управляемые/неуправляемые ресурсы?
@Progman Я говорю об удалении NativeMemory<T>
внутри Dispose(bool)
, оно должно быть размещено внутри if (disposing)
(т. е. управляемо) или снаружи (т. е. нативно)?
Что делает NativeMemoryManager
, где его определение? И почему удаление должно быть асинхронным?
Это специальный MemoryManager<T>, например stackoverflow.com/a/52191681/361899, только что реализовано асинхронное удаление для всех этих типов.
Я добавил его определение, чтобы не было путаницы, по сути, оно позволяет использовать память/диапазон в асинхронных сценариях.
NativeMemory<T>
— управляемый объект, владеющий неуправляемыми ресурсами. Так
NativeMemory<T>
должны быть удалены в ветке управляемого удаления (когда disposing==true
)NativeMemory<T>.Dispose()
нам нужны свободные неуправляемые ресурсы, принадлежащие этому экземпляру NativeMemory<T>
в ветке неуправляемого удаления (когда disposing==false
). (итак, NativeMemory.AlignedFree
должен находиться в ветке disposing==false
).P.S. Меня немного смущает поле NativeMemoryManager<T>
. Владеет ли он неуправляемой памятью или нет? Если настоящий владелец — NativeMemoryManager<T>
, то ответ должен быть другим.
П.П.С. Вы можете рассмотреть возможность реализации оболочки, унаследованной от SafeHandle или одного из его стандартных наследников. SafeHandle
может предоставить вам более надежную (при необходимости) управляемую оболочку для неуправляемых ресурсов.
Добавил определение диспетчера памяти, я бы сказал, что он не владеет памятью. Не уверен, что вы подразумеваете под использованием безопасной ручки в этом случае.
Исходя из данной реализации, я бы сказал, что NativeMemoryManager<T>
не владеет неуправляемым ресурсом (он просто предоставляет для него удобный интерфейс), поэтому ответ действителен. В то же время он реализует интерфейс IMemoryOwner<T>
(как и базовый класс), поэтому текущая реализация, не являющаяся владельцем, может нарушить какой-то контракт (и определенно нарушит семантику). Но я не могу для себя решить, какой способ изменения кода здесь будет лучшим.
@Progman Добавлены соответствующие помощники.