| Severity | Error |
| Reported by | PostSharp.Patterns.Caching |
| Message | The [InvalidateCache] aspect applied to <InvalidatingMethod> cannot invalidate <CachedMethod>: the 'this' parameter cannot be mapped because <CachedMethod> is an instance method, the IgnoreThisParameter property of its [Cache] or [CacheConfiguration] attribute is not set to true, and <InvalidatingMethod> is static or its declaring type <InvalidatingType> is not derived from <CachedType>. |
Cause
By default, the cache key of an instance method includes the this instance, so that two objects do not share their cached values. To remove a value from the cache, the InvalidateCacheAttribute aspect must rebuild the same key from the call to the invalidating method, <InvalidatingMethod>, and it uses the this instance of this call as the this instance of the cached method, <CachedMethod>. This only works when the invalidating method is an instance method of the type of the cached method, <CachedType>, or of a type derived from it.
PostSharp therefore reports CAC003, on <InvalidatingMethod>, when all of the following are true:
<CachedMethod>is an instance method;- the IgnoreThisParameter property is not set to
true, neither on the CacheAttribute aspect of<CachedMethod>nor on a CacheConfigurationAttribute of its type, its base types or its assembly; <InvalidatingMethod>is static, or its declaring type,<InvalidatingType>, is not derived from<CachedType>.
In the following example, PriceEditor.SetPrice invalidates ProductCatalog.GetPrice, whose cache key includes the ProductCatalog instance, but a PriceEditor is not a ProductCatalog.
using System;
using System.Collections.Generic;
using PostSharp.Patterns.Caching;
using PostSharp.Patterns.Caching.Backends;
namespace Errors.CAC003_Error;
public static class PriceStore
{
public static Dictionary<string, decimal> Prices { get; } = new() { ["TEA-01"] = 4.50m };
}
public class ProductCatalog
{
[Cache]
public decimal GetPrice( string productId )
{
Console.WriteLine( $"Reading the price of {productId}." );
return PriceStore.Prices[productId];
}
}
public class PriceEditor
{
[InvalidateCache( typeof(ProductCatalog), nameof(ProductCatalog.GetPrice) )]
public void SetPrice( string productId, decimal price ) // The cache key of GetPrice includes a ProductCatalog instance, which PriceEditor cannot supply.
{
PriceStore.Prices[productId] = price;
}
}
public static class Program
{
public static void Main()
{
CachingServices.DefaultService.Backend = new MemoryCachingBackend();
var catalog = new ProductCatalog();
var editor = new PriceEditor();
Console.WriteLine( catalog.GetPrice( "TEA-01" ) );
Console.WriteLine( catalog.GetPrice( "TEA-01" ) );
editor.SetPrice( "TEA-01", 5.00m );
Console.WriteLine( catalog.GetPrice( "TEA-01" ) );
}
}
error CAC003: CAC003_Error.cs(27,17): The [InvalidateCache] aspect applied to Errors.CAC003_Error.PriceEditor.SetPrice(System.String, System.Decimal) cannot invalidate Errors.CAC003_Error.ProductCatalog.GetPrice(System.String): the 'this' parameter cannot be mapped because Errors.CAC003_Error.ProductCatalog.GetPrice(System.String) is an instance method, the IgnoreThisParameter property of its [Cache] or [CacheConfiguration] attribute is not set to true, and Errors.CAC003_Error.PriceEditor.SetPrice(System.String, System.Decimal) is static or its declaring type Errors.CAC003_Error.PriceEditor is not derived from Errors.CAC003_Error.ProductCatalog.
Resolution
- If the value of the cached method does not depend on the instance, exclude the instance from the cache key: set the IgnoreThisParameter property of the
[Cache]aspect totrue, or set the IgnoreThisParameter property of a[CacheConfiguration]attribute on the type or the assembly. - If the value depends on the instance, move the invalidating method into the type of the cached method, or into a derived type, as an instance method.
- Otherwise, remove the
[InvalidateCache]aspect and invalidate the value imperatively with CachingServices.Invalidation, passing the instance of the cached method.
In the following example, the cache key of GetPrice no longer includes the ProductCatalog instance, so PriceEditor.SetPrice can invalidate it, and the call after SetPrice reads the new price.
using System;
using System.Collections.Generic;
using PostSharp.Patterns.Caching;
using PostSharp.Patterns.Caching.Backends;
namespace Errors.CAC003_Fixed;
public static class PriceStore
{
public static Dictionary<string, decimal> Prices { get; } = new() { ["TEA-01"] = 4.50m };
}
public class ProductCatalog
{
[Cache( IgnoreThisParameter = true )] // The cache key no longer includes the ProductCatalog instance.
public decimal GetPrice( string productId )
{
Console.WriteLine( $"Reading the price of {productId}." );
return PriceStore.Prices[productId];
}
}
public class PriceEditor
{
[InvalidateCache( typeof(ProductCatalog), nameof(ProductCatalog.GetPrice) )]
public void SetPrice( string productId, decimal price )
{
PriceStore.Prices[productId] = price;
}
}
public static class Program
{
public static void Main()
{
CachingServices.DefaultService.Backend = new MemoryCachingBackend();
var catalog = new ProductCatalog();
var editor = new PriceEditor();
Console.WriteLine( catalog.GetPrice( "TEA-01" ) );
Console.WriteLine( catalog.GetPrice( "TEA-01" ) );
editor.SetPrice( "TEA-01", 5.00m );
Console.WriteLine( catalog.GetPrice( "TEA-01" ) );
}
}
Reading the price of TEA-01.
4.50
4.50
Reading the price of TEA-01.
5.00
See Also
Reference
InvalidateCacheAttribute
IgnoreThisParameter
CacheConfigurationAttribute
Other Resources