Реализация IDisposable/IDisposeAsync как абстрактного базового класса [закрыто]C#

Место общения программистов C#
Ответить Пред. темаСлед. тема
Anonymous
 Реализация IDisposable/IDisposeAsync как абстрактного базового класса [закрыто]

Сообщение Anonymous »

РЕДАКТИРОВАТЬ: Я не уверен, почему этот вопрос был признан закрытым, поскольку комментарии и ответы, хотя и были в целом полезны, не затрагивали три маркированных вопроса (особенно вопрос о вызове Dispose изнутри DisposeAsyncCore, поскольку освобождение неуправляемых ресурсов централизовано в Dispose). Независимо от финализации, мой сценарий должен предполагать, что один из методов Dispose/disposeasync вызывается явно с помощью операторов using/await using.
Этот вопрос специфичен для многопоточных вычислений. приложения под .NET 9.
Я часто сталкиваюсь со сценариями COM Interop, где ссылки должны располагаться в определенном порядке, а порядок их получения недетерминирован. Я также придерживаюсь мнения, что наследование интерфейса — лучший подход по сравнению с наследованием реализации в сценариях, связанных с программными конструкциями, а не с бизнес-логикой. Однако этот вопрос не касается подхода.
Я использую конкретную, абстрактную реализацию шаблона IDisposable в качестве основы для классов верхнего уровня, требующих точного контроля над порядок утилизации. Я впервые реализую шаблон IAsyncDispose и стараюсь придерживаться документации, доступной здесь. Я все еще ломаю голову над этим, поэтому не уверен, подходит ли заголовок вопроса.
internal abstract class Disposable:
IDisposable,
IAsyncDisposable
{
private bool Disposed = false;

private readonly List SafeHandleList = [];
private readonly Stack SafeHandleStack = [];
private readonly Queue SafeHandleQueue = [];

private readonly List DisposableList = [];
private readonly Stack DisposableStack = [];
private readonly Queue DisposableQueue = [];

private readonly List AsyncDisposableList = [];
private readonly Stack AsyncDisposableStack = [];
private readonly Queue AsyncDisposableQueue = [];

protected Disposable () { }

~Disposable () { this.Dispose(disposing: false); }

public bool IsDisposed => this.Disposed;

protected T AddDisposable (T disposable) where T : IDisposable
{ this.ThrowDisposedException(); this.DisposableList.Add(disposable); return disposable; }

protected T PushDisposable (T disposable) where T : IDisposable
{ this.ThrowDisposedException(); this.DisposableStack.Push(disposable); return disposable; }

protected T EnqueueDisposable (T disposable) where T : IDisposable
{ this.ThrowDisposedException(); this.DisposableQueue.Enqueue(disposable); return disposable; }

protected T AddAsyncDisposable (T asyncDisposable) where T : IAsyncDisposable
{ this.ThrowDisposedException(); this.AsyncDisposableList.Add(asyncDisposable); if (asyncDisposable is IDisposable disposable) { this.DisposableList.Add(disposable); } return asyncDisposable; }

protected T PushAsyncDisposable (T asyncDisposable) where T : IAsyncDisposable
{ this.ThrowDisposedException(); this.AsyncDisposableStack.Push(asyncDisposable); if (asyncDisposable is IDisposable disposable) { this.DisposableStack.Push(disposable); } return asyncDisposable; }

protected T EnqueueAsyncDisposable (T asyncDisposable) where T : IAsyncDisposable
{ this.ThrowDisposedException(); this.AsyncDisposableQueue.Enqueue(asyncDisposable); if (asyncDisposable is IDisposable disposable) { this.DisposableQueue.Enqueue(disposable); } return asyncDisposable; }

protected T AddSafeHandle (T safeHandle) where T : SafeHandle
{ this.ThrowDisposedException(); this.SafeHandleList.Add(safeHandle); return safeHandle; }

protected T PushSafeHandle (T disposable) where T : SafeHandle
{ this.ThrowDisposedException(); this.SafeHandleStack.Push(disposable); return disposable; }

protected T EnqueueSafeHandle (T disposable) where T : SafeHandle
{ this.ThrowDisposedException(); this.SafeHandleQueue.Enqueue(disposable); return disposable; }

public void ThrowDisposedException ()
{
if (this.Disposed)
{
var type = this.GetType();

throw new ObjectDisposedException(type.FullName, $@"Attempt to access a disposed object: [{type.FullName}].");
}
}

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

protected virtual void Dispose (bool disposing)
{
if (!this.Disposed)
{
if (disposing)
{
// Dispose objects implementing [IDisposable] and [SafeHandle].

while (this.DisposableList.Count > 0) { try { this.DisposableList [0]?.Dispose(); } catch { } this.DisposableList.RemoveAt(0); }
while (this.DisposableStack.Count > 0) { try { this.DisposableStack.Pop()?.Dispose(); } catch { } }
while (this.DisposableQueue.Count > 0) { try { this.DisposableQueue.Dequeue()?.Dispose(); } catch { } }

while (this.SafeHandleList.Count > 0) { try { this.SafeHandleList [0]?.Dispose(); } catch { } this.SafeHandleList.RemoveAt(0); }
while (this.SafeHandleStack.Count > 0) { try { this.SafeHandleStack.Pop()?.Dispose(); } catch { } }
while (this.SafeHandleQueue.Count > 0) { try { this.SafeHandleQueue.Dequeue()?.Dispose(); } catch { } }

// This approach is meant to help both async and non-async consumption scenarios.
// Any objects implementing [IAsyncDisposable] as well as [IDisposable] have already been dealt with at this point.
// https://learn.microsoft.com/en-us/dotne ... e-patterns.
// It is up to the comsuming code to ensure that [DisposeAsync] is called, if needed. Example: [using (var ad = new AsyncDisposable())] vs. [await using (var ad = = new AsyncDisposable())].
}

// Dispose unmanaged resources (excluding [SafeHandle] objects).
// Free unmanaged resources (unmanaged objects), override finalizer, and set large fields to null.

this.Disposed = true;
}
}

public async ValueTask DisposeAsync ()
{
// Perform asynchronous cleanup.
await this.DisposeAsyncCore()
.ConfigureAwait(false);

// Dispose of unmanaged resources.
this.Dispose(false);

// Suppress finalization.
GC.SuppressFinalize(this);
}

protected virtual async ValueTask DisposeAsyncCore ()
{
if (!this.Disposed)
{
while (this.AsyncDisposableList.Count > 0)
{
var asyncDisposable = this.AsyncDisposableList [0];

if (asyncDisposable is not null) { try { await asyncDisposable.DisposeAsync().ConfigureAwait(false); } catch { } }
this.AsyncDisposableList.RemoveAt(0);
}

while (this.AsyncDisposableStack.Count > 0)
{
var asyncDisposable = this.AsyncDisposableStack.Pop();
if (asyncDisposable is not null) { try { await asyncDisposable.DisposeAsync().ConfigureAwait(false); } catch { } }
}

while (this.AsyncDisposableQueue.Count > 0)
{
var asyncDisposable = this.AsyncDisposableQueue.Dequeue();
if (asyncDisposable is not null) { try { await asyncDisposable.DisposeAsync().ConfigureAwait(false); } catch { } }
}

// TODO: We want to ensure that objects implementing only [IDisposable] are handled as well.
// Although there are not circular references between the dispose mthods, calling [this.Dispose()] here directly smells funny.
this.Dispose();

this.Disposed = true;
}
}
}

Хотя я старался придерживаться лучших практик, я не уверен в следующем:
  • Вызов Dispose из DisposeAsyncCore. Что-то меня это не устраивает. Возможно, я не продумал это как следует. Должен ли я вместо этого вызывать виртуальную реализацию Dispose(dispositing: true|false???)?
  • Есть ли какие-либо ошибки, если потребляющий код вызывает [using (var ad = new AsyncDisposable())] против [await using (var ad = new AsyncDisposable())]?
  • Я упускаю что-то очевидное с точки зрения охвата? Где следует утилизировать [SafeHandle] объекты идут? и т. д.
  • Любые общие комментарии по этой реализации как шаблону приветствуются.
Цель состоит в том, чтобы разрешить наследование классы, чтобы иметь возможность добавлять одноразовые объекты по своему усмотрению, без необходимости переопределять/нагружать свои собственные переопределения Dispose(bool)/DisposeAsyncCore.
Любые советы будут оценены.
Вот пример использования для пояснения:
public sealed class SampleTask:
Disposable
{
private readonly MemoryStream Stream1;
private readonly Excel.Application Application;

public SampleTask ()
{
// Add child objects in any desired order.
this.Application = this.AddDisposable(new Excel.Application());
this.Stream1 = this.PushAsyncDisposable(new MemoryStream());
}

// This class needs to have control over the lifetime of some
// exposed objects irrespective of their external reference count.
public Image GetImage () => this.AddDisposable(new Bitmap(10, 10));
public Excel.Worksheet GetExcelWorksheet () => this.AddDisposable(this.Application.Workbooks [0].Sheets [0]);

protected override void Dispose (bool disposing) => base.Dispose(disposing);
protected override ValueTask DisposeAsyncCore () => base.DisposeAsyncCore();
}


Подробнее здесь: https://stackoverflow.com/questions/792 ... base-class
Реклама
Ответить Пред. темаСлед. тема

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

  • Похожие темы
    Ответы
    Просмотры
    Последнее сообщение

Вернуться в «C#»