27

The behaviour of MemoryCache.AddOrGetExisting is described as:

Adds a cache entry into the cache using the specified key and a value and an absolute expiration value.

And that it returns:

If a cache entry with the same key exists, the existing cache entry; otherwise, null.

What is the purpose of a method with these semantics? What is an example of this?

abatishchev
  • 98,240
  • 88
  • 296
  • 433
Dave Hillier
  • 18,105
  • 9
  • 43
  • 87

3 Answers3

23

There are often situations where you only want to create a cache entry if a matching entry doesn't already exist (that is, you don't want to overwrite an existing value).

AddOrGetExisting allows you to do this atomically. Without AddOrGetExisting it would be impossible to perform the get-test-set in an atomic, thread-safe manner. For example:

 Thread 1                         Thread 2
 --------                         --------

 // check whether there's an existing entry for "foo"
 // the call returns null because there's no match
 Get("foo")

                                  // check whether there's an existing entry for "foo"
                                  // the call returns null because there's no match
                                  Get("foo")

 // set value for key "foo"
 // assumes, rightly, that there's no existing entry
 Set("foo", "first thread rulez")

                                  // set value for key "foo"
                                  // assumes, wrongly, that there's no existing entry
                                  // overwrites the value just set by thread 1
                                  Set("foo", "second thread rulez")

(See also the Interlocked.CompareExchange method, which enables a more sophisticated equivalent at the variable level, and also the wikipedia entries on test-and-set and compare-and-swap.)

LukeH
  • 263,068
  • 57
  • 365
  • 409
  • 1
    Nice explanation. But why would we use just `Add` then? Isn't it better to always use `AddOrGetExisting`? – Jose Luis May 27 '15 at 14:22
  • 4
    It would not be strictly "impossible", since you could wrap the Get/Set calls within a lock statement. – rymdsmurf Nov 25 '16 at 10:32
9

LukeH's answer is correct. Because the other answers indicate that the the method's semantics could be interpreted differently, I think it's worth pointing out that AddOrGetExisting in fact will not update existing cache entries.

So this code

Console.WriteLine(MemoryCache.Default.AddOrGetExisting("test", "one", new CacheItemPolicy()) ?? "(null)");
Console.WriteLine(MemoryCache.Default.AddOrGetExisting("test", "two", new CacheItemPolicy()));
Console.WriteLine(MemoryCache.Default.AddOrGetExisting("test", "three", new CacheItemPolicy()));

will print

(null)
one
one

Another thing to be aware of: When AddOrGetExisting finds an existing cache entry, it will not dispose of the CachePolicy passed to the call. This may be problematic if you use custom change monitors that set up expensive resource tracking mechanisms. Normally, when a cache entry is evicted, the cache system calls Dipose() on your ChangeMonitors. This gives you the opportunity to unregister events and the like. When AddOrGetExisting returns an existing entry, however, you have to take care of that yourself.

Sven Künzler
  • 1,650
  • 1
  • 20
  • 22
4

I haven't actually used this, but I guess one possible use case is if you want to unconditionally update the cache with a new entry for a particular key and you want to explicitly dispose of the old entry being returned.

  • +1 for "when you want to explicitly dispose of the old entry" – Seph Feb 05 '13 at 06:51
  • 3
    This makes sense, but I wonder if it's true... no one seems to explain why it returns null the first time you call AddOrGetExisting. – Alex Dresko May 23 '14 at 19:36
  • Makes sense it returns null the first time - from the name, it does not exist so it adds - otherwise return. You can then also use this as a check if the value was already in or not. – user369142 Apr 19 '18 at 13:54
  • You already have the existing value, you are passing it in! Why would you need it in the return type apart from convenience. Yes it's bloody surprising, but there are valid usecases for having the old value. – Ryan Leach Aug 26 '21 at 07:01