Simple, flexible, and thread-safe key/value memory cache
Generally, the code is simple and easy to understand.

Concurrency

It's good that you have a dedicated Lock object. You might consider a more expressive name, but in this case I think you can get away with it.

As you have noticed, using the lock means you gain nothing from the ConcurrentDictionary'2. Since this is such a simple interface, I would keep the lock and ditch the ConcurrentDictionary unless you have a serious (and measurable) performance concern.

However, you should be aware that by locking while you call Set, you are blocking access while you wait for an operation to complete, the length of which you cannot know. Addressing this without changing the class' behaviour would be non-trivial, primarily because it would complicate the case where Set fails, but may be necessary if Set is slow or needs access to the cache itself. If you don't care how many times you call Set, you could just use ConcurrentDictionary.GetOrAdd as mentioned by CharlesNRice in a comment, which would simply things somewhat.

Other


It's good that you have provided some level of inline documentation. I would be inclined to add an explanation of the threading behaviour (e.g. blocks while calling Set, will only call Set once). "a thread-safe way" is pretty meaningless: it's much better to explain the guarantees you will make.
Do you anticipate needing to change Set? If not, you should consider making it readonly and assign it in a constructor. There is a design concern that changing Set means you can't know which version was used by any call to Get from another thread.
Using a Tuple&lt;int?, string&gt; as a union doesn't seem a great idea. Either implement a sound union as a class of struct, or consider having a separate cache for each. If you do wish to keep this API, you should enforce the assumption that exactly one of them is null by explicitly checking and throwing if this is not the case, since it represents an error on the part of the caller, and telling them them sooner rather than later will save them lots of misery and time spent debugging.