Qualia.Decorators 0.2.5

dotnet add package Qualia.Decorators --version 0.2.5                
NuGet\Install-Package Qualia.Decorators -Version 0.2.5                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Qualia.Decorators" Version="0.2.5" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Qualia.Decorators --version 0.2.5                
#r "nuget: Qualia.Decorators, 0.2.5"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install Qualia.Decorators as a Cake Addin
#addin nuget:?package=Qualia.Decorators&version=0.2.5

// Install Qualia.Decorators as a Cake Tool
#tool nuget:?package=Qualia.Decorators&version=0.2.5                

Qualia.Decorators Library

The Decorators library is a powerful tool for C# developers that allows you to intercept methods and execute code before and after.

The library uses attributes to mark the targeted methods and - behind the scenes - adds the desired behavior via decorator pattern, providing additional functionality without modifying the initial implementation.

Features

  • Memoize: Cache a method result for the lifetime of the object containing it.
  • MemCache: Cache a method result.
  • Lock: Lock a method, to make it thread safe.
  • Open to extensions: Add your own attributes, using custom logic.

Usage

//ex: Use Memoize on a method
public class Foo
{
    [Memoize]
    public void Bar()
    {
        // Your method implementation
    }
}
// To enable the attributes you need to add the targeted class as a service to the service collection
// and then call services.UseDecorators method. The targeted class must be added behind an interface.
public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<IFoo, Foo>();
    services.UseDecorators();
}

Please note that the Decorators library uses reflection and should be used judiciously considering its impact on performance.

Example of custom decorator behavior

To extend the library, simply implement DecoratorBehavior (or DecoratorBehaviorAsync), and create a corresponding attribute.

    public class Memoize : DecoratorBehavior
    {
        private readonly ConcurrentDictionary<string, object> _cache = new ConcurrentDictionary<string, object>();

        public override object Invoke<TDecorated>(DecoratorContext<TDecorated> context)
        {
            var key = KeyGenerator.CreateKey(context.TargetMethod, context.Args);
            var result = _cache.GetOrAdd(key, _ => Next(context));

            return result;
        }
    }

    public class MemoizeAttribute : DecorateAttribute
    {
        public MemoizeAttribute(string name = null) : base(typeof(Memoize), name) { }
    }

You can also inject services in your behaviors or use parameters for the attributes.

    public class MemCache : DecoratorBehavior
    {
        private ILogger<MemCache> _logger;
        private readonly IMemoryCache _cache;

        public MemCache(ILogger<MemCache> logger, IMemoryCache cache)
        {
            _logger = logger;
            _cache = cache;
        }

        public override object Invoke<TDecorated>(DecoratorContext<TDecorated> context)
        {
            var key = KeyGenerator.CreateKey(context.TargetMethod, context.Args);
            var result = _cache.GetOrCreate(key, entry => 
            {
                ConfigureExpiration(ref entry, context);

                return Next(context); 
            });

            return result;
        }

        private void ConfigureExpiration<TDecorated>(ref ICacheEntry entry, DecoratorContext<TDecorated> context)
        {
            var att = (context.AssociatedDecorateAttribute as MemCacheAttribute);

            if (att?.Expiration == MemCacheAttribute.ExpirationType.Absolute)
            {
                entry.AbsoluteExpirationRelativeToNow = att?.TimeSpan;
                return;
            }
            if (att?.Expiration == MemCacheAttribute.ExpirationType.Sliding)
            {
                entry.SlidingExpiration = att?.TimeSpan;
                return;
            }

            throw new InvalidOperationException("MemCache decorator behavior failed while determining attribute's expiration type.");
        }
    }

    public class MemCacheAttribute : DecorateAttribute
    {
        public TimeSpan? TimeSpan { get; set; }
        public ExpirationType Expiration { get; set; }

        public MemCacheAttribute(string name = null, string timespan = "1m", ExpirationType expiration = ExpirationType.Absolute) 
            : base(typeof(MemCache), name) 
        {
            TimeSpan = StringToTimeSpan.Parse(timespan);
            Expiration = expiration;
        }

        public enum ExpirationType { Absolute, Sliding }
    }

Additionally, async behaviors can be implemented.

    public class Lock : DecoratorBehaviorAsync
    {
        private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1);
        private readonly ConcurrentDictionary<string, SemaphoreSlim> _locks = new ConcurrentDictionary<string, SemaphoreSlim>();
        private readonly ConcurrentDictionary<string, int> _lockReferences = new ConcurrentDictionary<string, int>();

        public override async Task<TReturn> InvokeAsync<TDecorated, TReturn>(DecoratorContext<TDecorated> context)
        {
            var lockingKey = $"{nameof(TDecorated)}_{context.TargetMethod.Name}";

            try
            {
                await LockAsync(lockingKey);

                return await Next<TDecorated, TReturn>(context);
            }
            finally
            {
                await UnlockAsync(lockingKey);
            }
        }
    }

    public class LockAttribute : DecorateAttribute
    {
        public LockAttribute(string name = null) : base(typeof(Lock), name)
        { }
    }
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
0.2.5 113 9/2/2024
0.2.4 99 8/31/2024
0.2.3 95 8/31/2024
0.2.2 109 8/26/2024
0.2.1 117 8/25/2024
0.2.0 118 8/11/2024
0.1.0 132 4/1/2024