Skip to content
m4bwav edited this page Oct 1, 2026 · 1 revision

Every public type of 2.0.0, in its three namespaces. Each one already existed in 1.0.1 with the same members and parameter names: a reflection listing of the published 2.0.0 (netstandard2.0 build, on .NET Framework 4.8) matched PublicApi-1.0.1.txt in 46 of 46 lines. 1.0.0 installs no assembly, so 1.0.1 is the first version with an API. Where 2.0.0 answers differently from 1.0.1, the member says so.

Signatures that name Autofac or Castle types now mean Autofac 6.5 or later and Castle.Core 5.1 or later (1.0.1: Autofac 3.3 and Castle.Core 3.2).

CachingServiceWithAOP

CacheAttribute

Marks a method whose result is cached. It goes on the class's method: the interceptor reads it from the method that runs, not from the interface.

Member What it does
CacheAttribute() No lifetime of its own: the cache service's lifetime applies (five minutes for MemoryCacheService()).
CacheAttribute(long ticks) A lifetime in 100-nanosecond ticks (10 000 000 ticks are one second).
CacheAttribute(int hours, int minutes, int seconds) A lifetime in hours, minutes and seconds.
CacheAttribute(int days, int hours, int minutes, int seconds) A lifetime in days, hours, minutes and seconds.
TimeSpan? CacheTimeout { get; set; } The lifetime, or null for the service's own.

A lifetime of zero or less keeps nothing. One too large for a date (long.MaxValue ticks) never expires; 1.0.1 threw ArgumentOutOfRangeException there (Versions and upgrading). The attribute is declared with AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = true), so the compiler accepts it on a class or a property too, where it does nothing. An override of a [Cache] method is cached even without the attribute, but with the service's lifetime, not the base method's (Errors and edge cases).

AOPCachingInterceptor

A Castle IInterceptor. A call to a method that carries [Cache] goes to ICacheService.GetByInvocation, with the attribute's lifetime when it has one; every other call proceeds unchanged.

Member What it does
AOPCachingInterceptor(ICacheService cacheService) The interceptor over a cache service. Autofac builds it from the container's ICacheService.
void Intercept(IInvocation invocation) Proceeds, or answers from the cache.

AutofacCachingModule

An Autofac Module that registers MemoryCacheService as ICacheService and AOPCachingInterceptor as itself, both with Autofac's default lifetime (a new instance per resolve).

Member What it does
static IContainer BuildContainer() A container holding only the module's registrations.
protected override void Load(ContainerBuilder builder) Adds the two registrations.
using System;
using Autofac;
using CachingServiceWithAOP;
using CachingServiceWithAOP.CachingServices;

using var container = AutofacCachingModule.BuildContainer();
Console.WriteLine(container.Resolve<ICacheService>().GetType().Name);
Console.WriteLine(container.Resolve<AOPCachingInterceptor>().GetType().Name);
Console.WriteLine(ReferenceEquals(container.Resolve<ICacheService>(), container.Resolve<ICacheService>()));

On every runtime and dependency set, and on 1.0.1:

MemoryCacheService
AOPCachingInterceptor
False

Two MemoryCacheService instances still share their entries, because both use MemoryCache.Default (How results are cached).

CachingServiceWithAOP.CachingServices

ICacheService

The store the interceptor uses. Register your own implementation after RegisterCachingModule() to replace MemoryCacheService (Recipes).

Member What it does
T? Get<T>(string key) The value under key, or default(T) when there is none or it is of another type.
T? Get<T>(string key, Func<T> retrievalFunc) The value under key; on a miss runs retrievalFunc, stores its result and returns it.
void Set<T>(string key, T value) Stores value with the service's lifetime.
void GetByInvocation(IInvocation invocation, TimeSpan? duration = null) Answers an intercepted call from the store, or proceeds and stores its result for duration (null: the service's lifetime).

MemoryCacheService

The ICacheService over System.Runtime.Caching.

Constructor Store Lifetime
MemoryCacheService() MemoryCache.Default five minutes
MemoryCacheService(ObjectCache backingCache) the cache you pass five minutes (1.0.1: zero, so it kept nothing)
MemoryCacheService(long absoluteTicks) MemoryCache.Default the ticks
MemoryCacheService(int absoluteHours, int absoluteMinutes, int absoluteSeconds) MemoryCache.Default hours, minutes, seconds
MemoryCacheService(TimeSpan expiration) MemoryCache.Default the TimeSpan

Each one, storing a value and reading it straight back:

using System;
using System.Runtime.Caching;
using CachingServiceWithAOP.CachingServices;

var services = new (string Name, MemoryCacheService Service)[]
{
    ("()", new MemoryCacheService()),
    ("(TimeSpan.FromMinutes(1))", new MemoryCacheService(TimeSpan.FromMinutes(1))),
    ("(600000000L)", new MemoryCacheService(600000000L)),
    ("(0, 1, 0)", new MemoryCacheService(0, 1, 0)),
    ("(new MemoryCache(...))", new MemoryCacheService(new MemoryCache("mine"))),
    ("(TimeSpan.Zero)", new MemoryCacheService(TimeSpan.Zero)),
    ("(TimeSpan.FromMinutes(-5))", new MemoryCacheService(TimeSpan.FromMinutes(-5))),
    ("(TimeSpan.MaxValue)", new MemoryCacheService(TimeSpan.MaxValue)),
};

foreach (var (name, service) in services)
{
    var key = "key for " + name;
    try
    {
        service.Set(key, "kept");
        Console.WriteLine(name + ": " + (service.Get<string>(key) ?? "nothing"));
    }
    catch (Exception e)
    {
        Console.WriteLine(name + ": " + e.GetType().Name);
    }
}

On every runtime and dependency set:

(): kept
(TimeSpan.FromMinutes(1)): kept
(600000000L): kept
(0, 1, 0): kept
(new MemoryCache(...)): kept
(TimeSpan.Zero): nothing
(TimeSpan.FromMinutes(-5)): nothing
(TimeSpan.MaxValue): kept

Get and Set by key:

using System;
using CachingServiceWithAOP.CachingServices;

var cache = new MemoryCacheService();
cache.Set("answer", 42);
Console.WriteLine(cache.Get<int>("answer"));
Console.WriteLine(cache.Get<string>("answer") ?? "(null)");
Console.WriteLine(cache.Get<int>("missing"));
Console.WriteLine(cache.Get("answer", () => 7));
Console.WriteLine(cache.Get("answer", () => "seven") ?? "(null)");
Console.WriteLine(cache.Get<int>("answer"));

On every runtime and dependency set:

42
(null)
0
42
seven
0

Get<string> on an int gives the default, and so does a missing key: the two cases look the same. Get(key, func) treats a value of another type as a miss, runs the function and replaces the value, which is why the last line prints 0. More on Get(key, func) (null results, concurrent callers) is in How results are cached; the errors (Set with null, a null key) are in Errors and edge cases.

The package has no method that removes an entry.

IKeyService and DefaultCacheKeyService

IKeyService has one member, string GenerateUniqueKeyForCall(IInvocation invocation). DefaultCacheKeyService is the key MemoryCacheService uses; nothing in the package lets you plug in another IKeyService. You can call it from an interceptor of your own:

using System;
using Castle.DynamicProxy;
using CachingServiceWithAOP.CachingServices;

var shelf = new ProxyGenerator().CreateInterfaceProxyWithTarget<IShelf>(new Shelf(), new KeyPrinter());
shelf.Find("A-1", 2);
shelf.Top<decimal>(3);

public interface IShelf
{
    string Find(string sku, int page);

    T[] Top<T>(int count);
}

public class Shelf : IShelf
{
    public string Find(string sku, int page) => sku + "/" + page;

    public T[] Top<T>(int count) => new T[count];
}

public class KeyPrinter : IInterceptor
{
    public void Intercept(IInvocation invocation)
    {
        Console.WriteLine(new DefaultCacheKeyService().GenerateUniqueKeyForCall(invocation));
        invocation.Proceed();
    }
}

On every runtime and dependency set:

Shelf;Find;Argument Types:System.String,System.Int32,;values:"A-1"|2|;
Shelf;Top;Generic Arguments:System.Decimal,;Argument Types:System.Int32,;values:3|;

An argument that cannot be written into a key makes GenerateUniqueKeyForCall throw, as in 1.0.1; the interceptor catches that and runs the call uncached (Errors and edge cases).

CachingServiceWithAOP.Extensions

AutofacExtensions

Member What it does
static void RegisterCachingModule(this ContainerBuilder builder) builder.RegisterModule<AutofacCachingModule>().
static IRegistrationBuilder<TLimit, TActivatorData, TRegistrionStyle> EnableCacheInterception<TLimit, TActivatorData, TRegistrionStyle>(this IRegistrationBuilder<TLimit, TActivatorData, TRegistrionStyle> builder) EnableInterfaceInterceptors().InterceptedBy(typeof(AOPCachingInterceptor)). The type parameter's spelling is 1.0.1's.
static bool HasCacheAttribute(this IComponentRegistration registration) Whether a public method of the registration's implementation type carries [Cache].

ReflectionExtensions

Member What it does
static bool HasCacheAttribute(this MethodInfo method) Whether the method, or a method it overrides, carries [Cache].

Both HasCacheAttribute methods:

using System;
using Autofac;
using CachingServiceWithAOP;
using CachingServiceWithAOP.Extensions;

Console.WriteLine(typeof(Inventory).GetMethod("Count")!.HasCacheAttribute());
Console.WriteLine(typeof(IInventory).GetMethod("Count")!.HasCacheAttribute());

var builder = new ContainerBuilder();
builder.RegisterCachingModule();
builder.RegisterType<Inventory>().As<IInventory>().EnableCacheInterception();
using var container = builder.Build();
foreach (var registration in container.ComponentRegistry.Registrations)
{
    Console.WriteLine(registration.Activator.LimitType.Name + ": " + registration.HasCacheAttribute());
}

public interface IInventory
{
    int Count(string sku);
}

public class Inventory : IInventory
{
    [Cache]
    public int Count(string sku) => sku.Length;
}

With Autofac 6.5.0 (.NET 10, .NET 8 and .NET Framework 4.8):

True
False
Inventory: True
AOPCachingInterceptor: False
MemoryCacheService: False
LifetimeScope: False

With Autofac 9.3.4 the registrations come in another order:

True
False
LifetimeScope: False
MemoryCacheService: False
AOPCachingInterceptor: False
Inventory: True

Clone this wiki locally