Dispose(bool) для управляемой оболочки, в конце концов, она нативная или управляемая?

В документации по шаблону предлагается, как удалить объект:

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> следует ли считать его управляемым или неуправляемым?

@Progman Добавлены соответствующие помощники.

aybe 15.07.2024 21:48

Вы спрашиваете, что делать, когда вы ссылаетесь на объект NativeMemory<T> и хотите его удалить, или вы говорите о том, что находитесь внутри NativeMemory<T> и хотите удалить любые управляемые/неуправляемые ресурсы?

Progman 15.07.2024 22:31

@Progman Я говорю об удалении NativeMemory<T> внутри Dispose(bool), оно должно быть размещено внутри if (disposing) (т. е. управляемо) или снаружи (т. е. нативно)?

aybe 15.07.2024 23:30

Что делает NativeMemoryManager, где его определение? И почему удаление должно быть асинхронным?

Charlieface 16.07.2024 13:37

Это специальный MemoryManager<T>, например stackoverflow.com/a/52191681/361899, только что реализовано асинхронное удаление для всех этих типов.

aybe 16.07.2024 15:19

Я добавил его определение, чтобы не было путаницы, по сути, оно позволяет использовать память/диапазон в асинхронных сценариях.

aybe 16.07.2024 15:25
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
6
63
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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 может предоставить вам более надежную (при необходимости) управляемую оболочку для неуправляемых ресурсов.

Добавил определение диспетчера памяти, я бы сказал, что он не владеет памятью. Не уверен, что вы подразумеваете под использованием безопасной ручки в этом случае.

aybe 16.07.2024 15:26

Исходя из данной реализации, я бы сказал, что NativeMemoryManager<T> не владеет неуправляемым ресурсом (он просто предоставляет для него удобный интерфейс), поэтому ответ действителен. В то же время он реализует интерфейс IMemoryOwner<T> (как и базовый класс), поэтому текущая реализация, не являющаяся владельцем, может нарушить какой-то контракт (и определенно нарушит семантику). Но я не могу для себя решить, какой способ изменения кода здесь будет лучшим.

Serg 16.07.2024 16:05

Другие вопросы по теме