Setting dictionary tuple values directly

Viewed 189

Is it possible to do something similar to this: dictTupleTest[key].Item1 = toggle; in the following situation?

Dictionary<int, (bool, bool)> dictTupleTest = new Dictionary<int, (bool, bool)>();
var key = 3;
var toggle = false;

dictTupleTest.Add(key, (true, false));

//This works
dictTupleTest[key] = (toggle, dictTupleTest[key].Item2);

//While this gives an error
dictTupleTest[key].Item1 = toggle;

The error: Error CS1612: Cannot modify the return value of 'Dictionary<int, (bool, bool)>.this[int]' because it is not a variable.

Or is there a better way to do it?

2 Answers

Tuples are immutable; the fact that it's stored in a dictionary is irrelevant. You'd get the same error with:

var x = dictTupleTest[key];
x.Item1 = toggle;

If you want to change one of the values, then don't use a tuple - use a mutable class. Otherwise, the way you're doing it is appropriate (keeping the second value).

EDIT -

Thanks to Theodor Zoulias for pointing out that my reasoning was flawed. The tuple is mutable, but for some reason (I'm not sure why), you can't change a property of the tuple inline with a dictionary accessor. That error is more common when you try to use mutation operators on a return value (like dictTupleTest[key]++), but I don't see why calling a property set shouldn't be allowed.

In any case, assigning the result to a variable does work:

dictTupleTest.Add(key, (true, false));
var x = dictTupleTest[key];
x.Item1 = false;

Console.WriteLine(dictTupleTest[key]);  // outputs (false, false)

Starting from .NET 6, it is possible to update a mutable value-type stored in a Dictionary<TKey,TValue> without doing more than one operations on the dictionary. The new API is the CollectionsMarshal.GetValueRefOrNullRef method:

Gets either a reference to a TValue in the Dictionary<TKey,TValue>, or a reference null if it does not exist in the dictionary.

It can be used like this:

using System.Runtime.InteropServices;
//...
CollectionsMarshal.GetValueRefOrNullRef(dictTupleTest, key).Item1 = toggle;

In case the key is not found in the dictionary, the above code will throw a NullReferenceException. For better control you can use a ref local like this:

using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
//...
ref var entry = ref CollectionsMarshal.GetValueRefOrNullRef(dictTupleTest, key);
if (Unsafe.IsNullRef(ref entry)) throw new KeyNotFoundException();
entry.Item1 = toggle;

The CollectionsMarshal.GetValueRefOrNullRef API is non easily discoverable, and this is intentional:

This is niche unsafe API that 99% of .NET developers should not ever use. We do not want to encourage people to use it just because of they can.

Related