Open sandboxFocusImprove this doc

Metalama.Patterns.Memoization

Memoization is an optimization technique that enhances the performance of deterministic methods by caching their results. Metalama provides a straightforward and high-performance implementation through the [Memoize] aspect.

This aspect is limited to get-only properties and parameterless methods. The cached value of memoized methods and properties is stored in a field of the object itself, enabling a high-performance implementation using Interlocked.CompareExchange. It serves as an alternative to the Lazy<T> class, offering simpler usage and superior performance characteristics.

To memoize a property or a method:

  1. Add the Metalama.Patterns.Memoization package to your project.
  2. Apply the [Memoize] attribute to the get-only property or parameterless method.
Warning

The [Memoize] aspect doesn't guarantee that the method will be executed only once. However, it ensures that it always returns the same value or object.

Note

For nullable reference types and value types, the cached value is stored in a StrongBox<T>, adding some memory allocation overhead when many memoized properties or methods are evaluated. However, this allows for minimal memory allocation when few or none of them are evaluated.

Example: Memoization

The following example demonstrates typical use of the [Memoize] aspect. It presents a HashedBuffer class where the Hash property and the ToString method are optimized for performance. The example assumes that these members are evaluated for only a minority of HashedBuffer instances, so the hash shouldn't be pre-computed in the constructor. However, when they're evaluated, the example assumes they're evaluated often, so caching the result is beneficial. The [Memoize] aspect offers a solution that's both simpler and more performant than the Lazy<T> class.

Source Code
1using Metalama.Patterns.Memoization;
2using System;
3using System.IO.Hashing;
4
5namespace Doc.Memoize_;

6
7public class HashedBuffer
8{
9    public HashedBuffer( ReadOnlyMemory<byte> buffer )
10    {
11        this.Buffer = buffer;
12    }
13
14    public ReadOnlyMemory<byte> Buffer { get; }
15
16    [Memoize]
17    public ReadOnlyMemory<byte> Hash => XxHash64.Hash( this.Buffer.Span );
18
19    [Memoize]














20    public override string ToString() => $"{{HashedBuffer ({this.Buffer.Length} bytes)}}";
21}
Transformed Code
1using Metalama.Patterns.Memoization;
2using System;
3using System.IO.Hashing;
4using System.Runtime.CompilerServices;
5
6namespace Doc.Memoize_;
7
8public class HashedBuffer
9{
10    public HashedBuffer(ReadOnlyMemory<byte> buffer)
11    {
12        this.Buffer = buffer;
13    }
14
15    public ReadOnlyMemory<byte> Buffer { get; }
16
17    [Memoize]
18    public ReadOnlyMemory<byte> Hash
19    {
20        get
21        {
22            if (_Hash == null)
23            {
24                var value = new StrongBox<ReadOnlyMemory<byte>>(Hash_Source);
25                global::System.Threading.Interlocked.CompareExchange(ref this._Hash, value, null);
26            }
27
28            return _Hash.Value;
29        }
30    }
31
32    private ReadOnlyMemory<byte> Hash_Source => XxHash64.Hash(this.Buffer.Span);
33
34    [Memoize]
35    public override string ToString()
36    {
37        if (_ToString == null)
38        {
39            string value;
40            value = $"{{HashedBuffer ({this.Buffer.Length} bytes)}}";
41            global::System.Threading.Interlocked.CompareExchange(ref this._ToString, value, null);
42        }
43
44        return _ToString;
45    }
46
47    private StrongBox<ReadOnlyMemory<byte>> _Hash;
48    private string _ToString;
49}

Memoization vs. caching

Memoization can be considered a simple form of caching. The [Memoize] aspect is often a no-brainer—it's extremely simple to use and requires no infrastructure.

Factor Memoization Caching
Scope Local to a single class instance within the current process Either local or shared, when run as an external service such as Redis
Unicity of cache items Specific to the current instance or type Based on explicit string cache keys
Complexity and overhead Minimal overhead Significant overhead related to the generation of cache keys and, in the case of distributed caching, serialization
Expiration and invalidation No expiration or invalidation Advanced and configurable expiration policies and invalidation APIs