---
title: "MemoryCache, DistributedCache and HybridCache"
description: "The latest preview (.NET 9 preview 4) brought another caching structure to the .NET world - so let's order some things here."
updated: 2024-05-20
tags: [".NET 9", ".NET", "C#", "Cache"]
canonical: "https://steven-giesel.com/blogPost/e3ee537a-f70b-43d3-8d13-5cf81bc4406b/memorycache-distributedcache-and-hybridcache"
---

# MemoryCache, DistributedCache and HybridCache

The latest preview (.NET 9 preview 4) brought another caching structure to the .NET world - so let's order some things here.

## What is a `MemoryCache`? 
The `MemoryCache` is a datastructure that allows you, well, to cache objects in memory. It is a simple key-value store. A more in detailed blog post can be found here. *"[Caching in .NET with MemoryCache](https://steven-giesel.com/blogPost/b355a042-2c5b-4d96-b1ca-a9dc0fc23509/caching-in-net-with-memorycache)"*.

Here a simple example:
```csharp
public async Task<IActionResult> GetBlogPost(int id)
{
    // Cache key
    var cacheKey = $"BlogPost_{id}";

    // Check if the cache contains the blog post
    if (!_memoryCache.TryGetValue(cacheKey, out BlogPost blogPost))
    {
        // Retrieve the blog post from the repository
        blogPost = await _blogRepository.GetBlogPostByIdAsync(id);

        // Save the blog post in the cache
        _memoryCache.Set(cacheKey, blogPost);
    }

    return Ok(blogPost);
}
```

This can be simplified to:
```csharp
public async Task<IActionResult> GetBlogPost(int id)
{
    // Cache key
    var cacheKey = $"BlogPost_{id}";

    var blogPost = await _memoryCache.GetOrCreateAsync(cacheKey, async entry =>
    {
        return await _blogRepository.GetBlogPostByIdAsync(id);
    });

    return Ok(blogPost);
}
```

Of course there is a whole lot more to it - you can define how long your cache entry lives, with a multitude of strategies.
A oversimplied version of the `MemoryCache` would be: `ConcurrentDictionary<string, object>`. Keep that in mind!

## `DistributedCache`
The `IDistributedCache` is generally taken to communicate between multiple services (so multiple instances of an ASP.NET Web API Backend) and/or if you need to persist the data over the lifetime of your application (so after you shutdown and restarted your server).
A famous example would be: **Redis**. So if you register something like this in your code:
```csharp
builder.Services.AddStackExchangeRedisCache(...);
```

You register an implementation of `IDistributedCache` into your application. But you are also free to choose a database as your cache provider. For example an [**SqlServerCache**](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.sqlservercachingservicesextensions.adddistributedsqlservercache):
```csharp
builder.Services.AddDistributedSqlServerCache(options =>
{
    options.ConnectionString = builder.Configuration.GetConnectionString(
        "DistCache_ConnectionString");
    options.SchemaName = "dbo";
    options.TableName = "TestCache";
});
```

Okay - so far we know that the `IMemoryCache` lives and dies with the host application lifecycle (aka your app) and isn't well suited for distributed or load-balanced scenarios. Another fundemental difference between those two:
 * `IMemoryCache` - we will persist the live object in the same scope as the host lifetime!
 * `IDistributedCache` - we will have to serialize and deserialize the object (can be off box)!

That might make some scenarios difficult where you have non-serializble objects or objects that are expensive to serialize.

Now, look at the following code:
```csharp
public class SomeService(IDistributedCache cache)
{
    public async Task<SomeInformation> GetSomeInformationAsync(string name, int id, CancellationToken token = default)
    {
        var key = $"someinfo:{name}:{id}"; // unique key for this combination
        var bytes = await cache.GetAsync(key, token); // try to get from cache
        SomeInformation info;
        if (bytes is null)
        {
            // cache miss; get the data from the real source
            info = await SomeExpensiveOperationAsync(name, id, token);

            // serialize and cache it
            bytes = SomeSerializer.Serialize(info);
            await cache.SetAsync(key, bytes, token);
        }
        else
        {
            // cache hit; deserialize it
            info = SomeSerializer.Deserialize<SomeInformation>(bytes);
        }
        return info;
    }

    // this is the work we're trying to cache
    private async Task<SomeInformation> SomeExpensiveOperationAsync(string name, int id,
        CancellationToken token = default)
    { /* ... */ }

    // ...
}
```
<sup>Source: https://github.com/dotnet/AspNetCore.Docs/issues/32361</sup>

The `IDistributedCache` doesn't have a nice API for simply putting and retrieving elements, giving you a chance to lots of things wrong.
There is another problem: While `SomeExpensiveOperationAsync` might take some time, your `GetSomeInformationAsync` method is called multiple times. This can lead to [**Cache Stampede**](https://en.wikipedia.org/wiki/Cache_stampede):
> A cache stampede is a type of cascading failure that can occur when massively parallel computing systems with caching mechanisms come under a very high load. This behaviour is sometimes also called dog-piling

<sup>Source: https://en.wikipedia.org/wiki/Cache_stampede</sup>

So we call `SomeExpensiveOperationAsync` unneccessarily often and put our system under load, while waiting for the cache to populate might be the better strategy! Therefore meet:

## `HybridCache`
That is where the new type `HybridCache` will come into play. Officially introduced with .NET 9 preview - but available (thanks to `netstandard2.0`) even for .NET Framework 4.7.2.

The call from above can be done like this:
```csharp
public class SomeService(HybridCache cache)
{
    public async Task<SomeInformation> GetSomeInformationAsync(string name, int id, CancellationToken token = default)
    {
        return await cache.GetOrCreateAsync(
            $"someinfo:{name}:{id}", // unique key for this combination
            async cancel => await SomeExpensiveOperationAsync(name, id, cancel),
            token: token
        );
    }
}
```

That looks very much like the API of the `IMemoryCache` but behaves like the `IDistrubtedCache` "under the hood". Here are some other features, highlighted in the introduction:

> As you might expect for parity with IDistributedCache, HybridCache supports explicit removal by key (cache.RemoveKeyAsync(...)). HybridCache
> also introduces new optional APIs for IDistributedCache implementations, to avoid byte[] allocations (this feature is implemented
by the preview versions of Microsoft.Extensions.Caching.StackExchangeRedis and Microsoft.Extensions.Caching.SqlServer).
>  
> Serialization is configured as part of registering the service, with support for type-specific and generalized serializers via the
.WithSerializer(...) and .WithSerializerFactory(...) methods, chained from the AddHybridCache(...) call. By default, the library
handles string and byte[] internally, and uses System.Text.Json for everything else, but if you want to use protobuf, xml, or anything
else: that's easy to do.

<sup>Source: https://github.com/dotnet/AspNetCore.Docs/issues/32361#issuecomment-2070480937</sup>

A very nice addition to reduce potential issues one can have with `IDistributedCache`.