diff --git a/src/libraries/Microsoft.Extensions.Caching.Abstractions/src/CacheEntryExtensions.cs b/src/libraries/Microsoft.Extensions.Caching.Abstractions/src/CacheEntryExtensions.cs index e8c72b3ec37d9..14a5a7dc32de7 100644 --- a/src/libraries/Microsoft.Extensions.Caching.Abstractions/src/CacheEntryExtensions.cs +++ b/src/libraries/Microsoft.Extensions.Caching.Abstractions/src/CacheEntryExtensions.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.Primitives; namespace Microsoft.Extensions.Caching.Memory @@ -93,7 +94,7 @@ public static ICacheEntry RegisterPostEvictionCallback( { ThrowHelper.ThrowIfNull(callback); - return entry.RegisterPostEvictionCallback(callback, state: null); + return entry.RegisterPostEvictionCallbackNoValidation(callback, state: null); } /// @@ -110,6 +111,14 @@ public static ICacheEntry RegisterPostEvictionCallback( { ThrowHelper.ThrowIfNull(callback); + return entry.RegisterPostEvictionCallbackNoValidation(callback, state); + } + + private static ICacheEntry RegisterPostEvictionCallbackNoValidation( + this ICacheEntry entry, + PostEvictionDelegate callback, + object? state) + { entry.PostEvictionCallbacks.Add(new PostEvictionCallbackRegistration() { EvictionCallback = callback, @@ -172,12 +181,24 @@ public static ICacheEntry SetOptions(this ICacheEntry entry, MemoryCacheEntryOpt entry.AddExpirationToken(expirationToken); } - foreach (PostEvictionCallbackRegistration postEvictionCallback in options.PostEvictionCallbacks) + for (int i = 0; i < options.PostEvictionCallbacks.Count; i++) { - entry.RegisterPostEvictionCallback(postEvictionCallback.EvictionCallback!, postEvictionCallback.State); + PostEvictionCallbackRegistration postEvictionCallback = options.PostEvictionCallbacks[i]; + if (postEvictionCallback.EvictionCallback is null) + ThrowNullCallback(i, nameof(options)); + + entry.RegisterPostEvictionCallbackNoValidation(postEvictionCallback.EvictionCallback, postEvictionCallback.State); } return entry; } + + [DoesNotReturn] + private static void ThrowNullCallback(int index, string paramName) + { + string message = + $"MemoryCacheEntryOptions.PostEvictionCallbacks contains a PostEvictionCallbackRegistration with a null EvictionCallback at index {index}."; + throw new ArgumentException(message, paramName); + } } } diff --git a/src/libraries/Microsoft.Extensions.Caching.Memory/tests/MemoryCacheSetAndRemoveTests.cs b/src/libraries/Microsoft.Extensions.Caching.Memory/tests/MemoryCacheSetAndRemoveTests.cs index e7ac1edb71ece..f1002232e4c18 100644 --- a/src/libraries/Microsoft.Extensions.Caching.Memory/tests/MemoryCacheSetAndRemoveTests.cs +++ b/src/libraries/Microsoft.Extensions.Caching.Memory/tests/MemoryCacheSetAndRemoveTests.cs @@ -406,6 +406,29 @@ public void ClearClears() } } + [Fact] + public void SetNullCallback_NotAllowed_ArgumentException() + { + var cache = CreateCache(); + const string someKey = "test"; + var entry = cache.CreateEntry(someKey); + + var options = new MemoryCacheEntryOptions(); + + var notNullCallback = new PostEvictionCallbackRegistration() + { + EvictionCallback = (_, _, _, _) => {} + }; + + options.PostEvictionCallbacks.Add(notNullCallback); + + var nullCallback = new PostEvictionCallbackRegistration(); + + options.PostEvictionCallbacks.Add(nullCallback); + + Assert.Throws(() => entry.SetOptions(options)); + } + [Fact] public void RemoveRemovesAndInvokesCallback() {