mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Add ReferenceCountedDisposable<T>
This class is meant to be used in the same types of places std::shared_ptr is used but with manual instead of automatic ref counting. SubStorage now uses the class to optionally dispose its base storage object.
This commit is contained in:
parent
b6eac5ddb5
commit
f02c84e8dd
5 changed files with 361 additions and 24 deletions
|
@ -199,8 +199,8 @@ Module,DescriptionStart,DescriptionEnd,Name,Summary
|
|||
2,6203,,InvalidOpenModeForWrite,
|
||||
|
||||
2,6300,6399,UnsupportedOperation,
|
||||
2,6302,,SubStorageNotResizable,
|
||||
2,6303,,SubStorageNotResizableMiddleOfFile,
|
||||
2,6302,,UnsupportedOperationInSubStorageSetSize,Attempted to resize a non-resizable SubStorage.
|
||||
2,6303,,UnsupportedOperationInResizableSubStorageSetSize,Attempted to resize a SubStorage that wasn't located at the end of the base storage.
|
||||
2,6304,,UnsupportedOperationInMemoryStorageSetSize,
|
||||
2,6306,,UnsupportedOperationInFileStorageOperateRange,
|
||||
2,6310,,UnsupportedOperationInAesCtrExStorageWrite,
|
||||
|
@ -247,7 +247,7 @@ Module,DescriptionStart,DescriptionEnd,Name,Summary
|
|||
2,6811,,RemapStorageMapFull,
|
||||
|
||||
2,6900,6999,BadState,
|
||||
2,6902,,SubStorageNotInitialized,
|
||||
2,6902,,NotInitialized,
|
||||
2,6905,,NotMounted,
|
||||
2,6906,,SaveDataIsExtending,
|
||||
|
||||
|
|
Can't render this file because it has a wrong number of fields in line 153.
|
|
@ -385,10 +385,10 @@ namespace LibHac.Fs
|
|||
|
||||
/// <summary>Error code: 2002-6300; Range: 6300-6399; Inner value: 0x313802</summary>
|
||||
public static Result.Base UnsupportedOperation { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 6300, 6399); }
|
||||
/// <summary>Error code: 2002-6302; Inner value: 0x313c02</summary>
|
||||
public static Result.Base SubStorageNotResizable => new Result.Base(ModuleFs, 6302);
|
||||
/// <summary>Error code: 2002-6303; Inner value: 0x313e02</summary>
|
||||
public static Result.Base SubStorageNotResizableMiddleOfFile => new Result.Base(ModuleFs, 6303);
|
||||
/// <summary>Attempted to resize a non-resizable SubStorage.<br/>Error code: 2002-6302; Inner value: 0x313c02</summary>
|
||||
public static Result.Base UnsupportedOperationInSubStorageSetSize => new Result.Base(ModuleFs, 6302);
|
||||
/// <summary>Attempted to resize a SubStorage that wasn't located at the end of the base storage.<br/>Error code: 2002-6303; Inner value: 0x313e02</summary>
|
||||
public static Result.Base UnsupportedOperationInResizableSubStorageSetSize => new Result.Base(ModuleFs, 6303);
|
||||
/// <summary>Error code: 2002-6304; Inner value: 0x314002</summary>
|
||||
public static Result.Base UnsupportedOperationInMemoryStorageSetSize => new Result.Base(ModuleFs, 6304);
|
||||
/// <summary>Error code: 2002-6306; Inner value: 0x314402</summary>
|
||||
|
@ -476,7 +476,7 @@ namespace LibHac.Fs
|
|||
/// <summary>Error code: 2002-6900; Range: 6900-6999; Inner value: 0x35e802</summary>
|
||||
public static Result.Base BadState { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => new Result.Base(ModuleFs, 6900, 6999); }
|
||||
/// <summary>Error code: 2002-6902; Inner value: 0x35ec02</summary>
|
||||
public static Result.Base SubStorageNotInitialized => new Result.Base(ModuleFs, 6902);
|
||||
public static Result.Base NotInitialized => new Result.Base(ModuleFs, 6902);
|
||||
/// <summary>Error code: 2002-6905; Inner value: 0x35f202</summary>
|
||||
public static Result.Base NotMounted => new Result.Base(ModuleFs, 6905);
|
||||
/// <summary>Error code: 2002-6906; Inner value: 0x35f402</summary>
|
||||
|
|
|
@ -1,31 +1,64 @@
|
|||
using System;
|
||||
using LibHac.Diag;
|
||||
|
||||
namespace LibHac.Fs
|
||||
{
|
||||
public class SubStorage2 : IStorage
|
||||
{
|
||||
private ReferenceCountedDisposable<IStorage> SharedBaseStorage { get; set; }
|
||||
private IStorage BaseStorage { get; }
|
||||
private long Offset { get; }
|
||||
private long Size { get; set; }
|
||||
public bool IsResizable { get; set; }
|
||||
private bool IsResizable { get; set; }
|
||||
|
||||
public SubStorage2(IStorage baseStorage, long offset, long size)
|
||||
{
|
||||
BaseStorage = baseStorage;
|
||||
Offset = offset;
|
||||
Size = size;
|
||||
IsResizable = false;
|
||||
|
||||
Assert.AssertTrue(IsValid());
|
||||
Assert.AssertTrue(Offset >= 0);
|
||||
Assert.AssertTrue(Size >= 0);
|
||||
}
|
||||
|
||||
public SubStorage2(SubStorage2 baseStorage, long offset, long size)
|
||||
public SubStorage2(ReferenceCountedDisposable<IStorage> sharedBaseStorage, long offset, long size)
|
||||
{
|
||||
BaseStorage = baseStorage.BaseStorage;
|
||||
Offset = baseStorage.Offset + offset;
|
||||
SharedBaseStorage = sharedBaseStorage.AddReference();
|
||||
BaseStorage = SharedBaseStorage.Target;
|
||||
Offset = offset;
|
||||
Size = size;
|
||||
IsResizable = false;
|
||||
|
||||
Assert.AssertTrue(IsValid());
|
||||
Assert.AssertTrue(Offset >= 0);
|
||||
Assert.AssertTrue(Size >= 0);
|
||||
}
|
||||
|
||||
public SubStorage2(SubStorage2 subStorage, long offset, long size)
|
||||
{
|
||||
BaseStorage = subStorage.BaseStorage;
|
||||
Offset = subStorage.Offset + offset;
|
||||
Size = size;
|
||||
IsResizable = false;
|
||||
|
||||
Assert.AssertTrue(IsValid());
|
||||
Assert.AssertTrue(Offset >= 0);
|
||||
Assert.AssertTrue(Size >= 0);
|
||||
Assert.AssertTrue(subStorage.Size >= offset + size);
|
||||
}
|
||||
|
||||
private bool IsValid() => BaseStorage != null;
|
||||
|
||||
public void SetResizable(bool isResizable)
|
||||
{
|
||||
IsResizable = isResizable;
|
||||
}
|
||||
|
||||
protected override Result DoRead(long offset, Span<byte> destination)
|
||||
{
|
||||
if (BaseStorage == null) return ResultFs.SubStorageNotInitialized.Log();
|
||||
if (!IsValid()) return ResultFs.NotInitialized.Log();
|
||||
if (destination.Length == 0) return Result.Success;
|
||||
|
||||
if (!IsRangeValid(offset, destination.Length, Size)) return ResultFs.OutOfRange.Log();
|
||||
|
@ -35,7 +68,7 @@ namespace LibHac.Fs
|
|||
|
||||
protected override Result DoWrite(long offset, ReadOnlySpan<byte> source)
|
||||
{
|
||||
if (BaseStorage == null) return ResultFs.SubStorageNotInitialized.Log();
|
||||
if (!IsValid()) return ResultFs.NotInitialized.Log();
|
||||
if (source.Length == 0) return Result.Success;
|
||||
|
||||
if (!IsRangeValid(offset, source.Length, Size)) return ResultFs.OutOfRange.Log();
|
||||
|
@ -45,24 +78,24 @@ namespace LibHac.Fs
|
|||
|
||||
protected override Result DoFlush()
|
||||
{
|
||||
if (BaseStorage == null) return ResultFs.SubStorageNotInitialized.Log();
|
||||
if (!IsValid()) return ResultFs.NotInitialized.Log();
|
||||
|
||||
return BaseStorage.Flush();
|
||||
}
|
||||
|
||||
protected override Result DoSetSize(long size)
|
||||
{
|
||||
if (BaseStorage == null) return ResultFs.SubStorageNotInitialized.Log();
|
||||
if (!IsResizable) return ResultFs.SubStorageNotResizable.Log();
|
||||
if (size < 0 || Offset < 0) return ResultFs.InvalidSize.Log();
|
||||
if (!IsValid()) return ResultFs.NotInitialized.Log();
|
||||
if (!IsResizable) return ResultFs.UnsupportedOperationInSubStorageSetSize.Log();
|
||||
if (!IsOffsetAndSizeValid(Offset, size)) return ResultFs.InvalidSize.Log();
|
||||
|
||||
Result rc = BaseStorage.GetSize(out long baseSize);
|
||||
Result rc = BaseStorage.GetSize(out long currentSize);
|
||||
if (rc.IsFailure()) return rc;
|
||||
|
||||
if (baseSize != Offset + Size)
|
||||
if (currentSize != Offset + Size)
|
||||
{
|
||||
// SubStorage cannot be resized unless it is located at the end of the base storage.
|
||||
return ResultFs.SubStorageNotResizableMiddleOfFile.Log();
|
||||
return ResultFs.UnsupportedOperationInResizableSubStorageSetSize.Log();
|
||||
}
|
||||
|
||||
rc = BaseStorage.SetSize(Offset + size);
|
||||
|
@ -76,10 +109,32 @@ namespace LibHac.Fs
|
|||
{
|
||||
size = default;
|
||||
|
||||
if (BaseStorage == null) return ResultFs.SubStorageNotInitialized.Log();
|
||||
if (!IsValid()) return ResultFs.NotInitialized.Log();
|
||||
|
||||
size = Size;
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
protected override Result DoOperateRange(Span<byte> outBuffer, OperationId operationId, long offset, long size, ReadOnlySpan<byte> inBuffer)
|
||||
{
|
||||
if (!IsValid()) return ResultFs.NotInitialized.Log();
|
||||
|
||||
if (size == 0) return Result.Success;
|
||||
|
||||
if (!IsOffsetAndSizeValid(Offset, size)) return ResultFs.OutOfRange.Log();
|
||||
|
||||
return base.DoOperateRange(outBuffer, operationId, Offset + offset, size, inBuffer);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
SharedBaseStorage?.Dispose();
|
||||
SharedBaseStorage = null;
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,7 +63,7 @@ namespace LibHac.FsSystem
|
|||
|
||||
protected override Result DoSetSize(long size)
|
||||
{
|
||||
if (BaseStorage == null) return ResultFs.SubStorageNotInitialized.Log();
|
||||
if (BaseStorage == null) return ResultFs.NotInitialized.Log();
|
||||
|
||||
// todo: Add IsResizable member
|
||||
// if (!IsResizable) return ResultFs.SubStorageNotResizable.Log();
|
||||
|
@ -76,7 +76,7 @@ namespace LibHac.FsSystem
|
|||
if (baseSize != Offset + Length)
|
||||
{
|
||||
// SubStorage cannot be resized unless it is located at the end of the base storage.
|
||||
return ResultFs.SubStorageNotResizableMiddleOfFile.Log();
|
||||
return ResultFs.UnsupportedOperationInResizableSubStorageSetSize.Log();
|
||||
}
|
||||
|
||||
rc = BaseStorage.SetSize(Offset + size);
|
||||
|
|
282
src/LibHac/ReferenceCountedDisposable.cs
Normal file
282
src/LibHac/ReferenceCountedDisposable.cs
Normal file
|
@ -0,0 +1,282 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
// This file was taken from the Roslyn repository
|
||||
// https://github.com/dotnet/roslyn/blob/55c7d2f800eff819198dc76892cb4e7574d563c8/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/ReferenceCountedDisposable.cs
|
||||
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace LibHac
|
||||
{
|
||||
/// <summary>
|
||||
/// A reference-counting wrapper which allows multiple uses of a single disposable object in code, which is
|
||||
/// deterministically released (by calling <see cref="IDisposable.Dispose"/>) when the last reference is
|
||||
/// disposed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Each instance of <see cref="ReferenceCountedDisposable{T}"/> represents a counted reference (also
|
||||
/// referred to as a <em>reference</em> in the following documentation) to a target object. Each of these
|
||||
/// references has a lifetime, starting when it is constructed and continuing through its release. During
|
||||
/// this time, the reference is considered <em>alive</em>. Each reference which is alive owns exactly one
|
||||
/// reference to the target object, ensuring that it will not be disposed while still in use. A reference is
|
||||
/// released through either of the following actions:</para>
|
||||
///
|
||||
/// <list type="bullet">
|
||||
/// <item>The reference is explicitly released by a call to <see cref="Dispose"/>.</item>
|
||||
/// <item>The reference is no longer in use by managed code and gets reclaimed by the garbage collector.</item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para>While each instance of <see cref="ReferenceCountedDisposable{T}"/> should be explicitly disposed when
|
||||
/// the object is no longer needed by the code owning the reference, this implementation will not leak resources
|
||||
/// in the event one or more callers fail to do so. When all references to an object are explicitly released
|
||||
/// (i.e. by calling <see cref="Dispose"/>), the target object will itself be deterministically released by a
|
||||
/// call to <see cref="IDisposable.Dispose"/> when the last reference to it is released. However, in the event
|
||||
/// one or more references is not explicitly released, the underlying object will still become eligible for
|
||||
/// non-deterministic release (i.e. finalization) as soon as each reference to it is released by one of the
|
||||
/// two actions described previously.</para>
|
||||
///
|
||||
/// <para>When using <see cref="ReferenceCountedDisposable{T}"/>, certain steps must be taken to ensure the
|
||||
/// target object is not disposed early.</para>
|
||||
///
|
||||
/// <list type="number">
|
||||
/// <para>Use <see cref="ReferenceCountedDisposable{T}"/> consistently. In other words, do not mix code using
|
||||
/// reference-counted wrappers with code that references to the target directly.</para>
|
||||
/// <para>Only use the <see cref="ReferenceCountedDisposable{T}(T)"/> constructor one time per target object.
|
||||
/// Additional references to the same target object must only be obtained by calling
|
||||
/// <see cref="TryAddReference"/>.</para>
|
||||
/// <para>Do not call <see cref="IDisposable.Dispose"/> on the target object directly. It will be called
|
||||
/// automatically at the appropriate time, as described above.</para>
|
||||
/// </list>
|
||||
///
|
||||
/// <para>All public methods on this type adhere to their pre- and post-conditions and will not invalidate state
|
||||
/// even in concurrent execution.</para>
|
||||
/// </remarks>
|
||||
/// <typeparam name="T">The type of disposable object.</typeparam>
|
||||
public sealed class ReferenceCountedDisposable<T> : IDisposable
|
||||
where T : class, IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// The target of this reference. This value is initialized to a non-<see langword="null"/> value in the
|
||||
/// constructor, and set to <see langword="null"/> when the current reference is disposed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>This value is only cleared in order to support cases where one or more references is garbage
|
||||
/// collected without having <see cref="Dispose"/> called.</para>
|
||||
/// </remarks>
|
||||
private T? _instance;
|
||||
|
||||
/// <summary>
|
||||
/// The boxed reference count, which is shared by all references with the same <see cref="Target"/> object.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>This field serves as the synchronization object for the current type, since it is shared among all
|
||||
/// counted reference to the same target object. Accesses to <see cref="StrongBox{T}.Value"/> should only
|
||||
/// occur when this object is locked.</para>
|
||||
///
|
||||
/// <para>PERF DEV NOTE: A concurrent (but complex) implementation of this type with identical semantics is
|
||||
/// available in source control history. The use of exclusive locks was not causing any measurable
|
||||
/// performance overhead even on 28-thread machines at the time this was written.</para>
|
||||
/// </remarks>
|
||||
private readonly StrongBox<int> _boxedReferenceCount;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new reference counting wrapper around an <see cref="IDisposable"/> object.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>The reference count is initialized to 1.</para>
|
||||
/// </remarks>
|
||||
/// <param name="instance">The object owned by this wrapper.</param>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// If <paramref name="instance"/> is <see langword="null"/>.
|
||||
/// </exception>
|
||||
public ReferenceCountedDisposable(T instance)
|
||||
: this(instance, new StrongBox<int>(1))
|
||||
{
|
||||
}
|
||||
|
||||
private ReferenceCountedDisposable(T instance, StrongBox<int> referenceCount)
|
||||
{
|
||||
_instance = instance ?? throw new ArgumentNullException(nameof(instance));
|
||||
|
||||
// The reference count has already been incremented for this instance
|
||||
_boxedReferenceCount = referenceCount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the target object.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>This call is not valid after <see cref="Dispose"/> is called. If this property or the target
|
||||
/// object is used concurrently with a call to <see cref="Dispose"/>, it is possible for the code to be
|
||||
/// using a disposed object. After the current instance is disposed, this property throws
|
||||
/// <see cref="ObjectDisposedException"/>. However, the exact time when this property starts throwing after
|
||||
/// <see cref="Dispose"/> is called is unspecified; code is expected to not use this property or the object
|
||||
/// it returns after any code invokes <see cref="Dispose"/>.</para>
|
||||
/// </remarks>
|
||||
/// <value>The target object.</value>
|
||||
public T Target => _instance ?? throw new ObjectDisposedException(nameof(ReferenceCountedDisposable<T>));
|
||||
|
||||
/// <summary>
|
||||
/// Increments the reference count for the disposable object, and returns a new disposable reference to it.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>The returned object is an independent reference to the same underlying object. Disposing of the
|
||||
/// returned value multiple times will only cause the reference count to be decreased once.</para>
|
||||
/// </remarks>
|
||||
/// <returns>A new <see cref="ReferenceCountedDisposable{T}"/> pointing to the same underlying object, if it
|
||||
/// has not yet been disposed; otherwise, <see cref="ObjectDisposedException"/> is thrown if this reference
|
||||
/// to the underlying object has already been disposed.</returns>
|
||||
public ReferenceCountedDisposable<T> AddReference()
|
||||
=> TryAddReferenceImpl(_instance, _boxedReferenceCount) ??
|
||||
throw new ObjectDisposedException(nameof(ReferenceCountedDisposable<T>));
|
||||
|
||||
/// <summary>
|
||||
/// Increments the reference count for the disposable object, and returns a new disposable reference to it.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>The returned object is an independent reference to the same underlying object. Disposing of the
|
||||
/// returned value multiple times will only cause the reference count to be decreased once.</para>
|
||||
/// </remarks>
|
||||
/// <returns>A new <see cref="ReferenceCountedDisposable{T}"/> pointing to the same underlying object, if it
|
||||
/// has not yet been disposed; otherwise, <see langword="null"/> if this reference to the underlying object
|
||||
/// has already been disposed.</returns>
|
||||
public ReferenceCountedDisposable<T>? TryAddReference()
|
||||
=> TryAddReferenceImpl(_instance, _boxedReferenceCount);
|
||||
|
||||
/// <summary>
|
||||
/// Provides the implementation for <see cref="TryAddReference"/> and
|
||||
/// <see cref="WeakReference.TryAddReference"/>.
|
||||
/// </summary>
|
||||
private static ReferenceCountedDisposable<T>? TryAddReferenceImpl(T? target, StrongBox<int> referenceCount)
|
||||
{
|
||||
lock (referenceCount)
|
||||
{
|
||||
if (referenceCount.Value == 0)
|
||||
{
|
||||
// The target is already disposed, and cannot be reused
|
||||
return null;
|
||||
}
|
||||
|
||||
if (target == null)
|
||||
{
|
||||
// The current reference has been disposed, so even though it isn't disposed yet we don't have a
|
||||
// reference to the target
|
||||
return null;
|
||||
}
|
||||
|
||||
checked
|
||||
{
|
||||
referenceCount.Value++;
|
||||
}
|
||||
|
||||
// Must return a new instance, in order for the Dispose operation on each individual instance to
|
||||
// be idempotent.
|
||||
return new ReferenceCountedDisposable<T>(target, referenceCount);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases the current reference, causing the underlying object to be disposed if this was the last
|
||||
/// reference.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>After this instance is disposed, the <see cref="TryAddReference"/> method can no longer be used to
|
||||
/// object a new reference to the target, even if other references to the target object are still in
|
||||
/// use.</para>
|
||||
/// </remarks>
|
||||
public void Dispose()
|
||||
{
|
||||
T? instanceToDispose = null;
|
||||
lock (_boxedReferenceCount)
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
// Already disposed; allow multiple without error.
|
||||
return;
|
||||
}
|
||||
|
||||
_boxedReferenceCount.Value--;
|
||||
if (_boxedReferenceCount.Value == 0)
|
||||
{
|
||||
instanceToDispose = _instance;
|
||||
}
|
||||
|
||||
// Ensure multiple calls to Dispose for this instance are a NOP.
|
||||
_instance = null;
|
||||
}
|
||||
|
||||
instanceToDispose?.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a weak reference to a <see cref="ReferenceCountedDisposable{T}"/> which is capable of
|
||||
/// obtaining a new counted reference up until the point when the object is no longer accessible.
|
||||
/// </summary>
|
||||
public struct WeakReference
|
||||
{
|
||||
/// <summary>
|
||||
/// DO NOT DISPOSE OF THE TARGET.
|
||||
/// </summary>
|
||||
private readonly WeakReference<T>? _weakInstance;
|
||||
private readonly StrongBox<int>? _boxedReferenceCount;
|
||||
|
||||
public WeakReference(ReferenceCountedDisposable<T> reference)
|
||||
: this()
|
||||
{
|
||||
if (reference == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(reference));
|
||||
}
|
||||
|
||||
T? instance = reference._instance;
|
||||
var referenceCount = reference._boxedReferenceCount;
|
||||
if (instance == null)
|
||||
{
|
||||
// The specified reference is already not valid. This case is supported by WeakReference (not
|
||||
// unlike `new System.WeakReference(null)`), but we return early to avoid an unnecessary
|
||||
// allocation in this case.
|
||||
return;
|
||||
}
|
||||
|
||||
_weakInstance = new WeakReference<T>(instance);
|
||||
_boxedReferenceCount = referenceCount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Increments the reference count for the disposable object, and returns a new disposable reference to
|
||||
/// it.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Unlike <see cref="ReferenceCountedDisposable{T}.TryAddReference"/>, this method is capable of
|
||||
/// adding a reference to the underlying instance all the way up to the point where it is finally
|
||||
/// disposed.</para>
|
||||
///
|
||||
/// <para>The returned object is an independent reference to the same underlying object. Disposing of
|
||||
/// the returned value multiple times will only cause the reference count to be decreased once.</para>
|
||||
/// </remarks>
|
||||
/// <returns>A new <see cref="ReferenceCountedDisposable{T}"/> pointing to the same underlying object,
|
||||
/// if it has not yet been disposed; otherwise, <see langword="null"/> if the underlying object has
|
||||
/// already been disposed.</returns>
|
||||
public ReferenceCountedDisposable<T>? TryAddReference()
|
||||
{
|
||||
WeakReference<T>? weakInstance = _weakInstance;
|
||||
if (weakInstance == null || !weakInstance.TryGetTarget(out var target))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
StrongBox<int>? referenceCount = _boxedReferenceCount;
|
||||
if (referenceCount == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return TryAddReferenceImpl(target, referenceCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue