Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Type-safe setting of objects with a dictionary that has a `Type` key

I have a generic dictionary of objects where the key is of type Type:

public class DynamicObject : IDictionary<Type, object>

The idea is that this object is shared in a plugin-based architecture and so a type (which could reside in a plugin .dll) is used as a key in order to prevent clashes between plugins. This type is also used to store metadata about the field (such as a description of that field or the actual type of that field).

Currently plugins need to set the value of fields on this object using code similar to this:

DynamicObject someObject;
string someValue;
someObject[typeof(UsernameField)] = someValue;

The only problem with this is that this isn't type safe - even though the type UsernameField is aware of the exact type of the value it is expecting (e.g. int or in this case string), the value supplied here is just typed as an object. I'd like to use generics to make setting / getting of properties type safe but I'm not sure how. So far the best I've come up with is this:

// Field is a base class for all types used as keys on DynamicObject
[Description("Username of the user")]
public class UsernameField : Field
{
    public static void Set(DynamicObject obj, string value)
    {
        obj[typeof(UsernameField)] = someValue;
    }
}

// To set fields
UsernameField.Set(obj, someValue);

This is type safe, however it means that each of my field types (e.g. UsernameField) has a nearly identical static Set method.

How can I have type-safe access to values in this way without having lots of nearly identical methods on each of my field types?

As an aside, is using Type as a key like this a good idea or are there hidden pitfalls that I'm not yet aware of?

like image 431
Justin Avatar asked Jan 09 '12 13:01

Justin


2 Answers

Define an interface that all your plugins must implement:

public interface IPlugin {
    string Name { get; }
    string Author { get; }
    string Description { get; }
    void Init();
}

And then use a Dictionary<Type, IPlugIn>.

Typically the interface is declared in a separate dll (like "MyCompany.MyProject.PlugIns.Contracts.dll").


EDIT: Ok, I think that I know what you mean now.

The trick is to have a generic class between Field and UsernameField with a generic Set method. The fact that Field is not generic, makes all the field types assignable to it. This would not be the case, if it was declared as Field<T>.

public abstract class Field
{
}

public abstract class GenericField<T> : Field
{
    public void Set(DynamicObject obj, T value)
    {
        obj[this.GetType()] = value;
    }
}

public class UsernameField : GenericField<string>
{
    #region Singleton Pattern

    public static readonly UsernameField Instance = new UsernameField();

    private UsernameField() { }

    #endregion

}

Because we need to call GetType in the Set method, we cannot declare it as static. Therefore, I used the singleton pattern.

Now, we can set the field in a type safe way:

UsernameField.Instance.Set(obj, "Joe");

ADDITION 1:

Since now the fields are singletons, you could use the fields as key of the dictionary instead of their type.

public class DynamicObject : IDictionary<Field, object> { }

And Set would become:

public void Set(DynamicObject obj, T value)
{
    obj[this] = value;
}

ADDITION 2:

You could also define DynamicObject like this:

public class DynamicObject : Dictionary<Field, object>
{
    public void Set<T>(GenericField<T> field, T value)
    {
        this[field] = value;
    }
}

Now you can set values like this:

obj.Set(UsernameField.Instance, "Sue");

This is type safe and seems more natural. The Set method in GenericField is obsolete now.

like image 140
Olivier Jacot-Descombes Avatar answered Oct 28 '22 08:10

Olivier Jacot-Descombes


Fields shouldn't have to know about a DynamicObject to get and set the value. You could make DyanmicObject handle the getting and setting of values, but I think a better approach is to treat the DynamicObject as a collection of Field instances, and each field has its own value. Something like this:

interface IField
{
    object Value { get; set; }
}

interface IField<T> : IField
{
    new T Value { get; set; }
}

abstract class BaseField<T> : IField<T>
{
    T _value;
    public T Value
    {
        get { return _value; }
        set
        {
            // could add stuff like OnValueChanging, IsValueValid, etc...
            this._value = value;
        }
    }

    object IField.Value
    {
        get { return Value; }
        set { Value = (T)value; }
    }
}

class DynamicObject : List<IField>
{
    public TField GetField<TField>() where TField : IField
    {
        return this.OfType<TField>().Single();
    }
}

And the usage would then be:

class UsernameField : BaseField<string> { }

[TestMethod]
public void test()
{
    var parent = new DynamicObject();
    var field = new UsernameField() { Value = "the username" };
    parent.Add(field);
    Assert.AreEqual("the username", parent.GetField<UsernameField>().Value);
}
like image 40
default.kramer Avatar answered Oct 28 '22 08:10

default.kramer