Type-safe Dictionary for various types

  • Assume the following situation: you have an object that can store any object based on a key (basically, IDictionary<string, object>). You want to store objects of various types into it that are not directly related. (For example, the dictionary can be an ASP.NET Session, or it can represent a dictionary that will be serialized to disk for persistent storage.)



    I don't want to create a single class that will contain all those objects, because they are not directly related, they come from different places. But if you store each object separately, it means you have to use casts when getting some value and there is no type-check when you're setting it.



    To solve this, I created a generic type that encapsulates the key along with the associated type and a couple of extension methods that use it:



    class TypedKey<T>
    {
    public string Name { get; private set; }

    public TypedKey(string name)
    {
    Name = name;
    }
    }

    static class DictionaryExtensions
    {
    public static T Get<T>(this IDictionary<string, object> dictionary, TypedKey<T> key)
    {
    return (T)dictionary[key.Name];
    }

    public static void Set<T>(this IDictionary<string, object> dictionary, TypedKey<T> key, T value)
    {
    dictionary[key.Name] = value;
    }
    }


    Usage:



    private static readonly TypedKey<int> AgeKey = new TypedKey<int>("age");



    dictionary.Get(AgeKey) > 18
    dictionary.Set(AgeKey, age)


    This has the type-safety (both on get and set) of using a property, while being backed by a dictionary that can store anything.



    What do you think about this pattern?


    Beautiful solution!

    I would use Convert.ChangeType rather than naively casting to T. Let the TypeDescriptor framework handle object conversions for you!

  • Your suggestion is not really type-safe as you can still pass a key of the wrong type. Therefore I would just use a normal (string) key. But I would add a generic TryGet method which takes account of the type. The setter needs not to be generic.



    static class DictionaryExtensions
    {
    public static T Get<T>(this IDictionary<string, object> dictionary, string key)
    {
    return (T)dictionary[key];
    }

    public static bool TryGet<T>(this IDictionary<string, object> dictionary,
    string key, out T value)
    {
    object result;
    if (dictionary.TryGetValue(key, out result) && result is T) {
    value = (T)result;
    return true;
    }
    value = default(T);
    return false;
    }

    public static void Set(this IDictionary<string, object> dictionary,
    string key, object value)
    {
    dictionary[key] = value;
    }
    }


    You can then use the dictionary like this.



    int age = 20;
    dictionary.Set("age", age);

    // ...

    age = dictionary.Get<int>("age");

    // or the safe way
    if (dictionary.TryGet("age", out age)) {
    Console.WriteLine("The age is {0}", age);
    } else {
    Console.WriteLine("Age not found or of wrong type");
    }


    Note that the compiler can infer the generic type when using TryGet.






    UPDATE



    In despite of my suggestion above, I must agree that your solution is elegant. Here is another suggestion which is based on your solution but which encapsulates the dictionary instead of providing a key. Well, it acts as wrapper and as key at the same time



    public class Property<T>
    {
    Dictionary<object, object> _dict;

    public Property (Dictionary<object, object> dict)
    {
    _dict = dict;
    }

    public T Value {
    get { return (T)_dict[this]; }
    set { _dict[this] = value; }
    }
    }


    Alternatively, a string key could be provided in the Property's constructor.



    You can use it like this



    private static readonly Dictionary<object, object> _properties = 
    new Dictionary<object, object>();
    private static readonly Property<int> _age = new Property<int>(_properties);

    ...

    _age.Value > 18
    _age.Value = age

    The problem with this is that you have to specify the type every time you use `Get()` and that it's possible to use the wrong type with `Set()`, which will cause a runtime exception on a later `Get()`. But you're right that my solution is not completely type-safe.

    I have picked up your original idea again and developed it a little bit further. Please see my update.

    That's certainly an interesting idea. Yeah, I think it's better than what I originally proposed. Although using the object itself as a key (with reference equality) wouldn't work in my cases, because the `Dictionary` is persisted. But as you mentioned, that can be easily modified.

License under CC-BY-SA with attribution


Content dated before 7/24/2021 11:53 AM