Qualia.Decorators
0.2.5
dotnet add package Qualia.Decorators --version 0.2.5
NuGet\Install-Package Qualia.Decorators -Version 0.2.5
<PackageReference Include="Qualia.Decorators" Version="0.2.5" />
paket add Qualia.Decorators --version 0.2.5
#r "nuget: Qualia.Decorators, 0.2.5"
// 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 | Versions 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. |
-
.NETStandard 2.0
- Microsoft.CSharp (>= 4.7.0)
- Microsoft.Extensions.Caching.Abstractions (>= 8.0.0)
- Microsoft.Extensions.Logging.Abstractions (>= 8.0.0)
- System.Reflection.DispatchProxy (>= 4.7.1)
- System.Text.Json (>= 8.0.4)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.