135

I am trying to re-write some code using Dictionary to use ConcurrentDictionary. I have reviewed some examples but I am still having trouble implementing the AddOrUpdate function. This is the original code:

    dynamic a = HttpContext;
    Dictionary<int, string> userDic = this.HttpContext.Application["UserSessionList"] as Dictionary<int, String>;

   if (userDic != null)
   {
      if (useDic.ContainsKey(authUser.UserId))
      {
        userDic.Remove(authUser.UserId);
      }
   }
  else
  {
     userDic = new Dictionary<int,string>();
  }
  userDic.Add(authUser.UserId, a.Session.SessionID.ToString());
  this.HttpContext.Application["UserDic"] = userDic;

I don't know what to add for the update portion:

userDic.AddOrUpdate(authUser.UserId,
                    a.Session.SessionID.ToString(),
                    /*** what to add here? ***/);

Any pointers would be appreciated.

Wahid Bitar
  • 13,776
  • 13
  • 78
  • 106
user438331
  • 1,507
  • 3
  • 11
  • 11

5 Answers5

255

You need to pass a Func which returns the value to be stored in the dictionary in case of an update. I guess in your case (since you don't distinguish between add and update) this would be:

var sessionId = a.Session.SessionID.ToString();
userDic.AddOrUpdate(
  authUser.UserId,
  sessionId,
  (key, oldValue) => sessionId);

I.e. the Func always returns the sessionId, so that both Add and Update set the same value.

BTW: there is a sample on the MSDN page.

M4N
  • 94,805
  • 45
  • 217
  • 260
  • 7
    I was seriously battling to just find a function to add or update to the same value. thaks – Zapnologica Nov 20 '14 at 11:10
  • 3
    Good answer. Just from the signature of the AddOrUpdate() displayed in Visual Studio you can only guess the meaning of the 2 parameters. However in the specific case, that @user438331 asks about, I think the solution in my answer using a simple indexer is better. – Niklas Peter Sep 26 '15 at 10:33
  • 8
    As @NiklasPeter points out (http://stackoverflow.com/a/32796165/8479), you're better off just using the normal indexer to overwrite the value, since in your case you're not interested in the existing value if any. Much more readable. – Rory May 30 '16 at 16:34
  • 6
    I'd recommend changing your answer to point users at @NiklasPeter 's answer. It's a much better solution. – Will Calderwood Sep 21 '17 at 11:14
  • Contrary to the suggestions pointing to other answers this seem to be the right answer, check https://stackoverflow.com/a/42013356/1759931 – Christian Aug 05 '22 at 15:56
83

I hope, that I did not miss anything in your question, but why not just like this? It is easier, atomic and thread-safe (see below).

userDic[authUser.UserId] = sessionId;

Store a key/value pair into the dictionary unconditionally, overwriting any value for that key if the key already exists: Use the indexer’s setter

(See: http://blogs.msdn.com/b/pfxteam/archive/2010/01/08/9945809.aspx)

The indexer is atomic, too. If you pass a function instead, it might not be:

All of these operations are atomic and are thread-safe with regards to all other operations on the ConcurrentDictionary. The only caveat to the atomicity of each operation is for those which accept a delegate, namely AddOrUpdate and GetOrAdd. [...] these delegates are invoked outside of the locks

See: http://blogs.msdn.com/b/pfxteam/archive/2010/01/08/9945809.aspx

Niklas Peter
  • 1,776
  • 17
  • 21
  • 5
    Yes atomic in that it happens in all at once and can't half happen or get interrupted. However not safe in that someone else can change it to something else just before your do, in which case there change is lost, and you don't know it happened, if you want to only change it if the value is what you expect then this wont do that for you. – trampster Aug 07 '18 at 11:43
37

I ended up implementing an extension method:

static class ExtensionMethods
{
    // Either Add or overwrite
    public static void AddOrUpdate<K, V>(this ConcurrentDictionary<K, V> dictionary, K key, V value)
    {
        dictionary.AddOrUpdate(key, value, (oldkey, oldvalue) => value);
    }
}
steve cook
  • 3,116
  • 3
  • 30
  • 51
  • 7
    why doesn't the original implmentation have a method like this? – mcmillab Sep 15 '20 at 01:59
  • So, according to Niklas Peter reply above, and the phrase "The only caveat to the atomicity of each operation is for those which accept a delegate", is this extension method not atomically correct, or not? Thanks very much! – fdhsdrdark Aug 02 '23 at 11:54
1

For those who are interested in, I am currently implementing a case which is a great example for using the "oldValue" aka existing value instead of forcing a new one (personally I don't like the term "oldValue" as it is not that old when it was created just a few processor ticks ago from within a parallel thread).

dictionaryCacheQueues.AddOrUpdate(
    uid,
    new ConcurrentQueue<T>(),
    (existingUid, existingValue) => existingValue
);
Nicolas
  • 754
  • 8
  • 22
  • 7
    if you don't want to change the existing value you should use `GetOrAdd()` instead https://msdn.microsoft.com/en-us/library/ee378674(v=vs.110).aspx – Rory May 30 '16 at 16:29
  • 1
    Hm yes, you're right, GetOrAdd() is simpler and enough in this case - thx for this hint! – Nicolas May 30 '16 at 20:58
0

Easiest 2023 solution without introducing additional new variables:

userDic.AddOrUpdate(authUser.UserId,
                    a.Session.SessionID.ToString(),
                    (_, _) => a.Session.SessionID.ToString());

Enjoy!

PS: learn about the discard variable on MSDN.

Simon Mattes
  • 4,866
  • 2
  • 33
  • 53