Skip to content

Versions and Upgrading

m4bwav edited this page Oct 1, 2026 · 1 revision

Releases

From nuget.org, read on 2026-10-01 at 03:32 UTC. No version is deprecated or unlisted.

Version Published (UTC) Downloads Builds Depends on
2.0.0 2026-09-28 47 lib/net10.0, lib/netstandard2.0 Autofac 6.5.0+, Autofac.Extras.DynamicProxy 7.1.0+, Castle.Core 5.1.1+, System.Runtime.Caching 10.0.12+
2.0.0-beta.1 2026-09-28 32 the same the same (the release rehearsal of 2.0.0)
1.0.1 2015-04-18 2 313 lib/net45 Autofac.Extras.DynamicProxy2 3.0.5 (which brings Autofac 3.3.1 and Castle.Core 3.2.2)
1.0.0 2015-04-18 1 711 none (the files sit under bin/Debug)

The CHANGELOG dates 2.0.0 and 2.0.0-beta.1 on 2026-09-27, the maintainer's local date; nuget.org records both on 2026-09-28 in UTC.

1.0.0 installs nothing

Its package holds the build output under bin/Debug and obj/Debug instead of lib/, so a project that references it gets no assembly. The smallest program that names the namespace does not compile against 1.0.0 (on .NET 10):

using System;
using CachingServiceWithAOP;

Console.WriteLine(typeof(CacheAttribute).Assembly.GetName().Version);
error CS0246: The type or namespace name 'CachingServiceWithAOP' could not be found (are you missing a using directive or an assembly reference?)

Use 2.0.0.

1.0.1 works on .NET Framework only

1.0.1 is a net45 build over Autofac 3.3 and Castle.Core 3.2.2. A .NET 10 project can reference it (NuGet warns NU1701), but the first proxy fails. This program ran on both runtimes against both versions:

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

try
{
    var builder = new ContainerBuilder();
    builder.RegisterCachingModule();
    builder.RegisterType<Catalogue>().As<ICatalogue>().EnableCacheInterception();
    using var container = builder.Build();
    Console.WriteLine(container.Resolve<ICatalogue>().Title(7));
}
catch (Exception e)
{
    for (var error = e; error != null; error = error.InnerException)
    {
        Console.WriteLine(error.GetType().Name + ": " + error.Message);
    }
}

public interface ICatalogue
{
    string Title(int id);
}

public class Catalogue : ICatalogue
{
    [Cache]
    public string Title(int id) => "title " + id;
}

1.0.1 on .NET 10:

DependencyResolutionException: An exception was thrown while executing a resolve operation. See the InnerException for details. ---> The type initializer for 'Autofac.Extras.DynamicProxy2.RegistrationExtensions' threw an exception. (See inner exception for details.)
TypeInitializationException: The type initializer for 'Autofac.Extras.DynamicProxy2.RegistrationExtensions' threw an exception.
FileNotFoundException: Could not load file or assembly 'System.Security.Permissions, Version=0.0.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51'. The system cannot find the file specified.

1.0.1 on .NET Framework 4.8, and 2.0.0 on .NET 10, .NET 8 and .NET Framework 4.8:

title 7

MemoryCacheService on its own did work on .NET 10 with 1.0.1, since it needs no proxy.

What changed in 2.0.0

2.0.0 keeps every public type, member and parameter name of 1.0.1. What changes for a caller:

  • Dependencies: Autofac 6.5 or later, Autofac.Extras.DynamicProxy 7.1 or later (it replaces Autofac.Extras.DynamicProxy2) and Castle.Core 5.1.1 or later. The API names Autofac and Castle types, so code that compiled against 1.0.1 is compiled again against these.
  • Runtimes: .NET Framework 4.6.2 and later, .NET 8 and .NET 10, through netstandard2.0 and net10.0 builds. This wiki ran .NET Framework 4.8, .NET 8 and .NET 10.
  • Answers: the fixes below. Everything else answers as 1.0.1 did on .NET Framework (the golden replay at the end of this page).

The same programs on both versions, side by side.

Null and void results

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

var builder = new ContainerBuilder();
builder.RegisterCachingModule();
builder.RegisterType<Feed>().As<IFeed>().SingleInstance().EnableCacheInterception();
using var container = builder.Build();
var feed = container.Resolve<IFeed>();

for (var i = 0; i < 2; i++)
{
    try
    {
        Console.WriteLine("Latest: " + (feed.Latest() ?? "null"));
    }
    catch (Exception e)
    {
        Console.WriteLine("Latest: " + e.GetType().Name);
    }
}

try
{
    feed.Refresh();
    Console.WriteLine("Refresh: done");
}
catch (Exception e)
{
    Console.WriteLine("Refresh: " + e.GetType().Name);
}

public interface IFeed
{
    string? Latest();

    void Refresh();
}

public class Feed : IFeed
{
    [Cache]
    public string? Latest()
    {
        Console.WriteLine("Latest ran");
        return null;
    }

    [Cache]
    public void Refresh() => Console.WriteLine("Refresh ran");
}

1.0.1 on .NET Framework 4.8:

Latest ran
Latest: ArgumentNullException
Latest ran
Latest: ArgumentNullException
Refresh ran
Refresh: ArgumentNullException

2.0.0, every runtime and dependency set:

Latest ran
Latest: null
Latest: null
Refresh ran
Refresh: done

1.0.1 ran the method, then threw while storing its null result, so callers got an exception after the work was done. 2.0.0 keeps the null and runs void methods without storing anything.

Lifetimes too large for a date

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

var builder = new ContainerBuilder();
builder.RegisterCachingModule();
builder.RegisterType<Archive>().As<IArchive>().SingleInstance().EnableCacheInterception();
using var container = builder.Build();
var archive = container.Resolve<IArchive>();

for (var i = 0; i < 2; i++)
{
    try
    {
        Console.WriteLine("Read: " + archive.Read());
    }
    catch (Exception e)
    {
        Console.WriteLine("Read: " + e.GetType().Name);
    }
}

public interface IArchive
{
    int Read();
}

public class Archive : IArchive
{
    private int runs;

    [Cache(long.MaxValue)]
    public int Read()
    {
        Console.WriteLine("Read ran");
        return ++runs;
    }
}

1.0.1 on .NET Framework 4.8:

Read ran
Read: ArgumentOutOfRangeException
Read ran
Read: ArgumentOutOfRangeException

2.0.0, every runtime and dependency set:

Read ran
Read: 1
Read: 1

MemoryCacheService by key

The API reference programs, on 1.0.1 (.NET Framework 4.8 and .NET 10 alike). Get and Set:

42
(null)
0
42
(null)
42

2.0.0 prints seven and 0 for the last two lines: 1.0.1's Get(key, func) returned the default without running the function when the key held a value of another type. The constructors:

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

2.0.0 keeps the value in a MemoryCache you pass (1.0.1 gave it a lifetime of zero) and never expires TimeSpan.MaxValue.

The rest of the fixes

From the CHANGELOG; each one is also a case in the golden replay or a test in the repository:

  • Get(key, func) whose function returns null threw ArgumentNullException and ran the function again next time; the null is now kept.
  • Concurrent calls with one key all ran the method; now one runs and the others wait (How results are cached).
  • A faulted or cancelled Task stayed cached; it is now removed. ValueTask methods are not cached.
  • An argument that cannot be written into a key made the call throw before the method ran; the call now runs uncached (Errors and edge cases).
  • Interception without RegisterCachingModule() still fails, with Autofac's current error around the old one.

Moving from 1.0.1

  1. Reference 2.0.0. If the project references Autofac, Autofac.Extras.DynamicProxy2 or Castle.Core itself, move them to Autofac 6.5 or later, Autofac.Extras.DynamicProxy 7.1 or later (remove DynamicProxy2) and Castle.Core 5.1.1 or later.
  2. Rename using Autofac.Extras.DynamicProxy2; to using Autofac.Extras.DynamicProxy; where your code uses that namespace directly. Calls to RegisterCachingModule(), EnableCacheInterception() and [Cache] stay as they are.
  3. Your own Autofac code may need changes between Autofac 3 and 6; that is Autofac's upgrade, not this package's, and this wiki did not test it.
  4. Check the places where you relied on the old answers above: a MemoryCacheService(ObjectCache) that kept nothing, code that caught ArgumentNullException from a null result, and lifetimes of long.MaxValue ticks.
  5. A .NET Framework project must target 4.6.2 or later (1.0.1 accepted 4.5).

Cache keys are 1.0.1's, so nothing in the store format changes; nothing is persisted between processes anyway.

The golden replay

The repository keeps recordings of 1.0.1 answering 158 calls (tests/Golden, made with the published 1.0.1 by tests/Golden/Capture). For this wiki the capture project was copied to a scratch folder, built once for each version (only the pinned version changed for 2.0.0) and run per framework with --no-build, on Windows on 2026-09-30. Cases are compared by group and name, by parsed value, with the header left out.

Run today Compared with Identical Differing
1.0.1 on .NET Framework 4.8 1.0.1.net48-windows.json 158 0
1.0.1 on .NET 10 1.0.1.net10.0-windows.json 158 0
2.0.0 on .NET Framework 4.8 1.0.1.net48-windows.json 147 11
2.0.0 on .NET 10 1.0.1.net48-windows.json 141 17
2.0.0 on .NET 10 1.0.1.net10.0-windows.json 72 86
  • 1.0.1 still answers exactly as recorded, so the recordings still stand for it.
  • The 11 differences on .NET Framework are the 10 fixes listed above (null and void results, Get(key, func), the ObjectCache constructor, lifetimes of long.MaxValue, long.MinValue and TimeSpan.MaxValue) and the changed Autofac error for interception without the module. Each is a CHANGELOG line.
  • On .NET 10 the same 11 differ, plus 6 whose only difference is the (Parameter 'name') that .NET adds to ArgumentException messages.
  • The .NET 10 recording is 1.0.1 failing on its first proxy, so 2.0.0 differs from it wherever a proxy is made.

The public API, listed by reflection from the published 2.0.0 with the repository's tests/Golden/ApiList (net48, so the netstandard2.0 build), matched PublicApi-1.0.1.txt in 46 of 46 lines; 1.0.1 today matched it in 46 of 46 too.

Clone this wiki locally