Table of Contents

Class ReferencedResource<TKey, TValue>

Namespace
Acuit.Pinpoint.ResourceManagement
Assembly
Acuit.Pinpoint.ResourceManagement.Abstractions.dll

A base class for implementing IReferencedResource<TKey, TValue> that maintains the list of active references and manages their potentially simultaneous requests for resource values.

public abstract class ReferencedResource<TKey, TValue> : IReferencedResource<TKey, TValue>, IDisposable where TKey : notnull

Type Parameters

TKey

The type of the key used to identify different resources, which must be a non-nullable type.

TValue

The resource value type.

Inheritance
ReferencedResource<TKey, TValue>
Implements
IReferencedResource<TKey, TValue>
Derived
DerivedReferencedResource<TKey, TValue, TSourceValue>
Inherited Members

Examples

The following example demonstrates a ReferencedResource<TKey, TValue> implementation that caches values:

public interface IWidgetProvider
{
    IResourceReference<Widget> CreateWidgetReference(string widgetGroupName, string widgetItemName);
}

internal sealed record WidgetId(string WidgetGroupName, string WidgetItemName);

internal sealed class WidgetProvider(IMemoryCache memoryCache, IWidgetRetrievalService widgetRetrievalService)
    : ResourceProvider<WidgetId, Widget, ReferencedWidget>, IWidgetProvider
{
    public IResourceReference<Widget> CreateWidgetReference(string widgetGroupName, string widgetItemName) =>
        CreateResourceReference(new WidgetId(widgetGroupName, widgetItemName));

    protected override ReferencedWidget CreateReferencedResource(WidgetId key) => new(key, memoryCache, widgetRetrievalService);
}

internal sealed class ReferencedWidget(WidgetId key, IMemoryCache memoryCache, IWidgetRetrievalService widgetRetrievalService)
    : ReferencedResource<WidgetId, Widget>(key)
{
    private static readonly TimeSpan s_cachePeriod = TimeSpan.FromHours(1);

    private readonly ResourceChangeTracker _resourceChangeTracker = new();

    protected override void Dispose(bool disposing)
    {
        _resourceChangeTracker.Dispose();
        base.Dispose(disposing);
    }

    protected override async Task<Widget> GetValueAsync(CancellationToken cancellationToken)
    {
        var cacheKey = Tuple.Create(GetType(), Key); // ensure key will be unique across app
        return await memoryCache.GetOrCreateAsync(cacheKey, async entry =>
        {
            Widget widget = await widgetRetrievalService.RetrieveWidgetAsync(Key.WidgetGroupName, Key.WidgetItemName,
                cancellationToken).ConfigureAwait(false);
            _resourceChangeTracker.SignalChange();
            return widget;
        }, new MemoryCacheEntryOptions { AbsoluteExpirationRelativeToNow = s_cachePeriod }).ConfigureAwait(false)
            ?? throw new UnreachableException();
    }

    protected override IChangeToken GetChangeToken() => _resourceChangeTracker.GetChangeToken();
}

Remarks

To properly implement a derived class:

  1. Provide a constructor that calls the base class constructor with the necessary argument.
  2. Provide an override for GetValueAsync(CancellationToken) that produces the resource value. See the remarks for that method for details.
  3. Provide an override for GetChangeToken() that produces a change token that signals when the resource value has changed.

Consider deriving from DerivedReferencedResource<TKey, TValue, TSourceValue> or PolledReferencedResource<TKey, TValue>, as they provide additional functionality for common use cases.

Sometimes it is desirable to cache resource values provided by a resource provider to limit the frequency of expensive retrievals for resources that will not necessarily have long-lived resource references. For example, a resource specific to a particular unit might be retrieved upon a unit scan, immediately releasing the resource reference. When the retrieval of this resource is relatively expensive, it is beneficial to cache these values for some period of time. There is no built-in implementation for this because it is easily accomplished within your class derived from ReferencedResource<TKey, TValue> using an IMemoryCache or IDistributedCache, as demonstrated in the example below.

Constructors

ReferencedResource(TKey)

Initializes a new instance of the ReferencedResource<TKey, TValue> class.

protected ReferencedResource(TKey key)

Parameters

key TKey

The resource key.

Exceptions

ArgumentNullException

key is null.

Properties

Key

Gets the resource key.

public TKey Key { get; }

Property Value

TKey

Methods

CreateNewReference()

Creates a new reference to this resource.

public IResourceReference<TValue> CreateNewReference()

Returns

IResourceReference<TValue>

A new IResourceReference<TValue>, which should be disposed when the reference is no longer needed to remove the reference.

Exceptions

ObjectDisposedException

The object has been disposed.

Dispose()

Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.

public void Dispose()

Dispose(bool)

Closes and releases all resources used by the ReferencedResource<TKey, TValue>.

protected virtual void Dispose(bool disposing)

Parameters

disposing bool

true when this is in response to a call to Dispose().

Remarks

Derived classes should override this when they have any resources that should be disposed.

GetChangeToken()

Returns a IChangeToken that can be used to observe when this resource value changes.

protected abstract IChangeToken GetChangeToken()

Returns

IChangeToken

A IChangeToken.

GetValueAsync(CancellationToken)

Gets the resource value.

protected abstract Task<TValue> GetValueAsync(CancellationToken cancellationToken)

Parameters

cancellationToken CancellationToken

A cancellation token that can be used to request canceling retrieving the resource.

Returns

Task<TValue>

The task object representing the asynchronous operation.

Remarks

Once the operation completes, the Result property on the returned task object contains the resource value.

This method will be called on behalf of resource references, ensuring it is never called more than once at a time. The returned value will be cached and returned to all references until the change token (retrieved via GetChangeToken() before calling GetValueAsync(CancellationToken)) signals a change.

If this method throws an exception, it will be propagated to resource references (via their call to GetValueAsync(CancellationToken)), and subsequent calls to GetValueAsync(CancellationToken) by resource references will cause this method to be called again.

Exceptions

Exception

The resource could not be retrieved. Specific exceptions depend on the implementation.

Events

LastReferenceRemoved

Raised when the last reference to this resource is removed.

public event EventHandler<LastReferenceRemovedEventArgs<TKey>>? LastReferenceRemoved

Event Type

EventHandler<LastReferenceRemovedEventArgs<TKey>>