Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Acceptable to use a Type for a Dictionary Key?

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?

like image 971
Rachel Avatar asked Nov 19 '13 14:11

Rachel


People also ask

Do dictionary keys need to be the same type?

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.

What data types can be dictionary keys?

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.

Which of the following Cannot be used as a key in a dictionary?

We can use integer, string, tuples as dictionary keys but cannot use list as a key of it .

What can be used as dictionary keys in Python?

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.


2 Answers

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.

like image 124
Marc Gravell Avatar answered Oct 19 '22 06:10

Marc Gravell


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.)

like image 35
Jon Skeet Avatar answered Oct 19 '22 05:10

Jon Skeet