HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpModerate

Type-safe Dictionary for various types

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
typeforsafetypesdictionaryvarious

Problem

Assume the following situation: you have an object that can store any object based on a key (basically, IDictionary). 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
{
    public string Name { get; private set; }

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

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

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


Usage:

private static readonly TypedKey AgeKey = new TypedKey("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?

Solution

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(this IDictionary dictionary, string key)
    {
        return (T)dictionary[key];
    }

    public static bool TryGet(this IDictionary 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 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("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
{
    Dictionary _dict;

    public Property (Dictionary 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 _properties = 
    new Dictionary();
private static readonly Property _age = new Property(_properties);

...

_age.Value > 18
_age.Value = age

Code Snippets

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;
    }
}
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");
}
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; }
    }
}
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

Context

StackExchange Code Review Q#12291, answer score: 17

Revisions (0)

No revisions yet.