Why returning Task<T> creates heap allocations even for synchronously completed operations:
Task<T> is a class on the managed heap. If a cached method completes synchronously 90% of the time, creating a new Task<T> on every call generates useless GC Gen-0 garbage.
When to use ValueTask<T>:
public ValueTask<int> GetCachedCountAsync()
{
if (_cache.TryGetValue("count", out int val))
return new ValueTask<int>(val); // 0 Heap Allocations! Wrapped in a stack struct!
return new ValueTask<int>(FetchCountFromDatabaseAsync());
}Golden Rule: Use ValueTask<T> when the method completes synchronously in hot paths (e.g. cache hits, buffered stream reads).