I would like to make a class that can store at most one copy of an object. All the objects stored here will share the same base class, and I would like to be able to get an object based on it's type.
I've come up with this solution so far, but I feel like I'm doing something wrong with using a Type for the Dictionary key.
Base class used in multiple modules
interface ISessionVariables { }
Example of common singleton class used for accessing
public class SessionVariables
{
private object _sync = new object();
private Dictionary<Type, ISessionVariables> _sessionVariables =
new Dictionary<Type, ISessionVariables>;
public T Get<T>()
where T : ISessionVariable, new()
{
lock (_sync)
{
ISessionVariables rtnValue = null;
if (_sessionVariables.TryGetValue(typeof(T), out rtnValue))
return (T)rtnValue;
rtnValue = new T();
_sessionVariables.Add(typeof(T), rtnValue);
return (T)rtnValue;
}
}
}
This way I can call it like this from individual modules
SessionVariableSingleton.Get<ModuleASessionVars>().PropertyA;
SessionVariableSingleton.Get<ModuleCSessionVars>().PropertyC;
Is this an acceptable way of storing this kind of data structure? Or is there a better alternative using a List or a dictionary without a Type key?
Keys are unique within a dictionary while values may not be. The values of a dictionary can be of any type, but the keys must be of an immutable data type such as strings, numbers, or tuples.
Dictionary keys must be of an immutable type. Strings and numbers are the two most commonly used data types as dictionary keys. We can also use tuples as keys but they must contain only strings, integers, or other tuples.
We can use integer, string, tuples as dictionary keys but cannot use list as a key of it .
Strings, numbers, and tuples work as keys, and any type can be a value. Other types may or may not work correctly as keys (strings and tuples work cleanly since they are immutable). Looking up a value which is not in the dict throws a KeyError -- use "in" to check if the key is in the dict, or use dict.
Yes Type
is fine as a key; thread-safety is a concern, though - in many ways Hashtable
is better at threaded-scenarios. However, there is a better option since you are using generics: cheat:
class SessionVariables {
static class Cache<T> where T : ISessionVariable, new() {
public static readonly ISessionVariable Value = new T();
}
ISessionVariable Get<T>() where T : ISessionVariable, new() {
return Cache<T>.Value;
}
}
Which is now fully thread-safe (without "returned different instances" issues) without any dictionary costs.
Edit on the topic of Hashtable
for Jon:
Dictionary<TKey,TValue>
makes no guarantees on concurrency, so you are required to synchronize all access - including the reads, as another thread doing a write can break a reader (you can force this in an example, but like most thread-races, it is hard to reproduce).
By contract, Hashtable
guarantees that it is safe for any number of readers, plus at most one writer. From MSDN:
Hashtable is thread safe for use by multiple reader threads and a single writing thread. It is thread safe for multi-thread use when only one of the threads perform write (update) operations, which allows for lock-free reads provided that the writers are serialized to the Hashtable.
This means that you can do things like:
var val = (SomeType)hash[key];
if(val == null) {
// not there; actually compute / create the value
val = ...
// and store it for the next access
lock(syncLock) {
hash[key] = val; // note: could do double-check here
}
}
return val;
Notice that the read cycle above does not require any synchronization; only the writes need to be synchronized. Note also that because Hashtable
uses object
, it works best when the keys and values are classes (not structs).
Yes, concurrent dictionaries now exist - but the above works just fine.
There's one issue: it's not thread-safe. Given that you appear to be using this via a singleton, I'd imagine you do need it to be thread-safe.
If you're using .NET 4, you'd be best off using ConcurrentDictionary
. Otherwise, add locking.
The ConcurrentDictionary
code would look like this:
internal class SessionVariables
{
private readonly ConcurrentDictionary<Type, ISessionVariables> dictionary
= new ConcurrentDictionary<Type, ISessionVariables>();
internal ISessionVariable Get<T>() where T : ISessionVariable, new()
{
return dictionary.GetOrAdd(typeof(T), _ => new T());
}
}
(I'd also avoid singletons where possible, but that's a different matter.)
EDIT: To specifically address using a type as a dictionary key: yes, that's fine. In fact, you're fine with reference equality, as Type
objects are effectively canonically - there's only one Type
object to represent a particular type in a particular AppDomain
, no matter how you ask for it. (This may not be true in dynamically generated assemblies using CodeDOM... not sure. But it should be fine for the normal case.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With