-
Notifications
You must be signed in to change notification settings - Fork 4.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make Microsoft.Extensions.Caching.Memory.MemoryCache support generics #54771
Comments
Tagging subscribers to this area: @eerhardt, @maryamariyan, @michaelgsharp Issue DetailsBackground and MotivationWe use MemoryCache extensively in our project and found that MemoryCache could greatly benefit from 2 features:
Making MemoryCache support generics can solve both issues, since we can enforce TKey to be IEquatable Proposed APITo fully preserve backward compatibility, we can make a generic interface, and make original MemoryCache implement that generic interface: namespace Microsoft.Extensions.Caching.Memory
{
+ public interface IMemoryCache<TKey, TValue> : IDisposable {
} namespace Microsoft.Extensions.Caching.Memory
{
- public interface IMemoryCache : IDisposable {
+ public interface IMemoryCache : IMemoryCache<object, object> {
} namespace Microsoft.Extensions.Caching.Memory
{
+ public class MemoryCache<TKey, TValue> : IMemoryCache<TKey, TValue> where TKey : IEquatable<TKey> {
} namespace Microsoft.Extensions.Caching.Memory
{
public interface MemoryCache : IMemoryCache {
} Usage Examplesvar cache = new MemoryCache<string, string>(cacheOptions, comparer: StringComparer.OrdinalIgnoreCase);
cache["key"] = "value";
// Now this returns "value" as well
var value = cache["KEY"]; var cache = new MemoryCache<long, string>(cacheOptions);
// No more boxing
cache["value".GetHashCode()] = "value"; Alternative DesignsN/A RisksThe only risk is that this can potentially produce significant memory pressure if a consumer has a large number of different concrete MemoryCache<> types. Considering common usages of this cache, it is highly unlikely (Please correct me if I am wrong). I am glad to help publish a PR for the proposed change as we would love to consume it if the design is approved!
|
@davidfowl / @maryamariyan - this looks a lot like #48567, but only part of it. Do you think this public API refactoring could be done without any breaking changes? |
@eerhardt Not sure if the question is directed towards me, but IMHO, it should be possible to do this change without any breaking changes. The proposed design should already cover full backward compatibility (please correct me if I'm wrong) :) |
The part I'm concerned about is namespace Microsoft.Extensions.Caching.Memory
{
- public interface IMemoryCache : IDisposable {
+ public interface IMemoryCache : IMemoryCache<object, object> {
} This is probably a source breaking change, assuming public interface IMemoryCache<TKey, TValue> : IDisposable {
bool TryGetValue(TKey key, out TValue value);
ICacheEntry CreateEntry(TKey key);
void Remove(TKey key);
} The way it would be a break is if someone has an explicitly implemented interface: public class MyCache : IMemoryCache
{
bool IMemoryCache.TryGetValue(object key, out object value) { }
}
I'm not sure if it is would be a binary breaking change or not. Typically changing an interface's base interface is a breaking change, in general. See https://github.com/dotnet/runtime/blob/main/docs/coding-guidelines/breaking-changes.md#bucket-1-public-contract
|
Thanks for pointing out the issue! In that case, can we leave IMemoryCache/MemoryCache untouched, and start with public interface IMemoryCache<TKey, TValue> : IDisposable {
bool TryGetValue(TKey key, out TValue value);
ICacheEntry CreateEntry(TKey key);
void Remove(TKey key);
} Then, we can define the generic type with public class MemoryCache<TKey, TValue> : IMemoryCache, IMemoryCache<TKey, TValue>
{
...
} This should be similar to how containers in System.Collections and System.Collections.Generic behave, and should be fully backward compatible (since we are not changing existing memory cache implementation at all). Since MemoryCache and MemoryCache<TKey, TValue> may have lots of logic in common, we can probably further create a base class that hosts the common methods (let's say its name is MemoryCacheBase): + internal abstract class MemoryCacheBase {
- public class MemoryCache : IMemoryCache {
+ public class MemoryCache : MemoryCacheBase, IMemoryCache {
- public class MemoryCache<TKey, TValue> : IMemoryCache, IMemoryCache<TKey, TValue> {
+ public class MemoryCache<TKey, TValue> : MemoryCacheBase, IMemoryCache, IMemoryCache<TKey, TValue> { Or alternatively, maybe we can consider MemoryCache<TKey, TValue> as the new base class since MemoryCache is really a special case where both keys and values are objects: - public class MemoryCache : IMemoryCache {
+ public class MemoryCache : MemoryCache<object, object>, IMemoryCache { I assume inserting an internal base class is allowed according to the official guideline, since we are not changing the public facing contracts defined in interface. What do you think? |
You can insert a new base class to a class, but it needs to be public. You can't have a public class derive from an internal class. At this point of introducing a completely new interface, I think we are getting really close to duplicating #48567. |
Got it. Then we should let public class MemoryCache : MemoryCache<object, object>, IMemoryCache {
Agreed. The purpose of this issue is to introduce a generic version of |
@maryamariyan @davidfowl - any thoughts on whether this proposal should stand on its own, or be closed as a dupe of #48567? |
Closing as dupe of #48567 |
Background and Motivation
We use MemoryCache extensively in our project and found that MemoryCache could greatly benefit from 2 features:
Making MemoryCache support generics can solve both issues, since we can enforce TKey to be IEquatable
Proposed API
To fully preserve backward compatibility, we can make a generic interface, and make original MemoryCache implement that generic interface:
namespace Microsoft.Extensions.Caching.Memory { + public interface IMemoryCache<TKey, TValue> : IDisposable { }
namespace Microsoft.Extensions.Caching.Memory { + public class MemoryCache<TKey, TValue> : IMemoryCache<TKey, TValue> where TKey : IEquatable<TKey> { }
Usage Examples
Alternative Designs
N/A
Risks
The only risk is that this can potentially produce significant memory pressure if a consumer has a large number of different concrete MemoryCache<> types. Considering common usages of this cache, it is highly unlikely (Please correct me if I am wrong).
I am glad to help publish a PR for the proposed change as we would love to consume it if the design is approved!
The text was updated successfully, but these errors were encountered: