Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Oct 23, 2025

Summary

Fixes the documentation ambiguity in CacheExtensions.TryGetValue<TItem> and CacheExtensions.Get<TItem> to clarify that these methods handle type mismatches by returning false/default(TItem), not just when keys are absent.

Problem

The existing documentation stated that TryGetValue<TItem> returns "true if the key was found; false otherwise", which was ambiguous and misleading. In reality, the method returns false in two distinct scenarios:

  1. When the key doesn't exist in the cache
  2. When the key exists but the stored value can't be cast to TItem

This caused confusion, especially for instrumented cache decorators that track hit/miss metrics:

IMemoryCache cache = new MemoryCache(new MemoryCacheOptions());
cache.Set("key", (object)"string-value");

// Non-generic sees "hit" (returns true because key exists)
cache.TryGetValue("key", out object? obj);  // true

// Generic extension sees "miss" (returns false due to type mismatch)
cache.TryGetValue<int>("key", out var i);   // false

Changes

TryGetValue<TItem> Method

  • Summary: Updated to explicitly mention casting to TItem
  • Returns: Changed to "true if the key was found and the stored value can be cast to TItem; false otherwise"
  • Parameter: Clarified that value is set to default(TItem) for both missing keys and type mismatches
  • Remarks: Added comprehensive guidance explaining:
    • The method returns false for both missing keys and type mismatches
    • Users needing to distinguish these cases (e.g., for instrumentation) should use the non-generic IMemoryCache.TryGetValue method, which returns true if the key exists regardless of type

Get<TItem> Method

  • Summary: Updated to clarify it returns values only when "present and castable to TItem"
  • Returns: Added that default(TItem) is returned for both missing keys and type mismatches

Resolution

Addresses #38969 by providing clear documentation that helps developers understand the type-checking behavior and guides them toward appropriate alternatives when they need to distinguish between "key not found" and "key found with incompatible type" scenarios.

Original prompt

This section details on the original issue you should resolve

<issue_title>CacheExtensions.TryGetValue documentation and behavior are ambiguous for type mismatches (key present with incompatible type returns false)</issue_title>
<issue_description>### Description

When using Microsoft.Extensions.Caching.Memory.IMemoryCache with the generic extension method:

IMemoryCache cache = ...;
cache.Set("k", (object)"string-value");

// Under the hood: IMemoryCache.TryGetValue("k", out object? obj) == true
// But the generic extension below returns false because obj is not int.
bool found = cache.TryGetValue<int>("k", out var i); // found == false

This reveals two problems:

  1. Behavioral ambiguity for instrumentation / decorators
    A decorator that instruments IMemoryCache.TryGetValue(object, out object?) will observe a hit (returns true because the key exists), while the calling code using CacheExtensions.TryGetValue<T> observes a miss (false due to type mismatch). This makes consistent hit/miss accounting impossible when consumers use the generic extension.

  2. Documentation mismatch
    The current docs for CacheExtensions.TryGetValue<T> say: “true if the key was found; false otherwise.” In the scenario above, the key is found but the method returns false. The real behavior is closer to:

“Returns true if the key was found and the stored value is of type TItem (otherwise returns false and sets return value to default).”

Reproduction Steps

using Microsoft.Extensions.Caching.Memory;

var cache = new MemoryCache(new MemoryCacheOptions());

// Store as object/string
cache.Set("key", (object)"abc");

// 1) Non-generic call sees "found"
var foundObj = cache.TryGetValue("key", out object? obj); // true

// 2) Generic extension reports "not found" for incompatible type
var foundInt = cache.TryGetValue<int>("key", out var i);   // false

// Instrumented decorators counting "hits" in IMemoryCache.TryGetValue(object, out object?)
// will count the first as a hit, while callers using the generic extension see a miss.
Console.WriteLine((foundObj, foundInt)); // (True, False)

Expected behavior

Docs and API guidance should clarify that the generic overload only returns true when the value exists and is compatible with TItem.

Expected behavior

  • CacheExtensions.TryGetValue<T> documentation should explicitly state:

"Returns true if the key was found and the stored value can be cast to TItem. Returns false otherwise (including type mismatches)."

  • Guidance should explain implications for decorators/instrumented caches.
  • (Optional stretch) Consider an additional API if presence-vs-type disambiguation is needed, but a doc fix alone would resolve most confusion.

Actual behavior

  • Non-generic IMemoryCache.TryGetValue(object, out object?) returns true if the key is present.
  • Generic CacheExtensions.TryGetValue<T> returns false if the type does not match, even when the key exists.
  • Documentation implies only the first behavior ("true if the key was found”'), without mention of the type check.

Regression?

No. This appears to be the behavior since the generic extension was introduced. The issue is a documentation mismatch and missing guidance rather than a runtime regression

Known Workarounds

  • Use the non-generic overload when consistent hit/miss metrics are required in decorators.
  • Or, after a generic call returns false, double-check with the non-generic overload if you need to distinguish "key present with wrong type" from "key absent" scenario.

Configuration

No response

Other information

No response</issue_description>

<agent_instructions>Please fix the documentation based on the feedback provided in the issue.</agent_instructions>

Comments on the Issue (you are @copilot in this section)

@jozkee @rjmurillo thank you for reporting this. Your workaround is correct, and the documentation should be clearer about the type check in the "found" logic.

Fixes #11958


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI changed the title [WIP] Fix ambiguity in CacheExtensions.TryGetValue<T> behavior Clarify CacheExtensions.TryGetValue<TItem> documentation for type mismatch behavior Oct 23, 2025
Copilot AI requested a review from jozkee October 23, 2025 19:48
@dotnet-policy-service
Copy link
Contributor

Tagging subscribers to this area: @dotnet/area-extensions-caching

@gewarren gewarren marked this pull request as ready for review December 23, 2025 02:04
@gewarren gewarren requested a review from a team as a code owner December 23, 2025 02:04
Copilot AI review requested due to automatic review settings December 23, 2025 02:04
@gewarren gewarren merged commit 78e1b9c into main Dec 23, 2025
10 checks passed
@gewarren gewarren deleted the copilot/fix-cache-extensions-behavior branch December 23, 2025 02:04
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR clarifies the documentation for CacheExtensions.TryGetValue<TItem> and CacheExtensions.Get<TItem> methods to explicitly explain their behavior when type mismatches occur.

Key changes:

  • Updated method summaries to mention casting to TItem
  • Clarified return values to explain both missing key and type mismatch scenarios
  • Added comprehensive remarks explaining when to use non-generic alternatives

<remarks>To be added.</remarks>
<c>true</c> if the key was found and the stored value can be cast to <typeparamref name="TItem" />; <c>false</c> otherwise.</returns>
<remarks>
This method returns <c>false</c> both when the key is not present in the cache and when the key is present but the stored value can't be cast to <typeparamref name="TItem" />. If you need to distinguish between these two cases (for example, in instrumented caches tracking hit/miss metrics), use the non-generic <see cref="M:Microsoft.Extensions.Caching.Memory.IMemoryCache.TryGetValue(System.Object,System.Object@)" /> method instead, which returns <c>true</c> if the key is present regardless of type.
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The remark sentence is too long and should be broken up for better readability. Consider splitting it into separate sentences to improve clarity and follow documentation best practices for conciseness.

Copilot generated this review using guidance from repository custom instructions.
<remarks>To be added.</remarks>
<c>true</c> if the key was found and the stored value can be cast to <typeparamref name="TItem" />; <c>false</c> otherwise.</returns>
<remarks>
This method returns <c>false</c> both when the key is not present in the cache and when the key is present but the stored value can't be cast to <typeparamref name="TItem" />. If you need to distinguish between these two cases (for example, in instrumented caches tracking hit/miss metrics), use the non-generic <see cref="M:Microsoft.Extensions.Caching.Memory.IMemoryCache.TryGetValue(System.Object,System.Object@)" /> method instead, which returns <c>true</c> if the key is present regardless of type.
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to the documentation guidelines, use a conversational tone with contractions. Change "is not" to "isn't" to be consistent with the use of "can't" elsewhere in the sentence.

Copilot generated this review using guidance from repository custom instructions.
Gets the value associated with this key if present and castable to <typeparamref name="TItem" />.
</summary>
<returns>The value associated with this key, or <c>default(TItem)</c> if the key is not present.</returns>
<returns>The value associated with this key, or <c>default(TItem)</c> if the key is not present or the stored value can't be cast to <typeparamref name="TItem" />.</returns>
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to the documentation guidelines, use a conversational tone with contractions. Change "is not present" to "isn't present" to be consistent with the use of "can't" elsewhere in the documentation.

Copilot generated this review using guidance from repository custom instructions.
<param name="cache">The <see cref="T:Microsoft.Extensions.Caching.Memory.IMemoryCache" /> instance this method extends.</param>
<param name="key">The key of the value to get.</param>
<param name="value">The value associated with the given key.</param>
<param name="value">The value associated with the given key, or <c>default(TItem)</c> if the key is not found or the stored value can't be cast to <typeparamref name="TItem" />.</param>
Copy link

Copilot AI Dec 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to the documentation guidelines, use a conversational tone with contractions. Change "is not found" to "isn't found" to be consistent with the use of "can't" elsewhere in the documentation.

Copilot generated this review using guidance from repository custom instructions.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CacheExtensions.TryGetValue<T> documentation and behavior are ambiguous for type mismatches (key present with incompatible type returns false)

3 participants