使用具有“Type”键的字典对类型安全设置

时间:2012-01-09 13:53:13

标签: c# generics dictionary type-safety

我有一个对象的通用字典,其中键的类型为Type

public class DynamicObject : IDictionary<Type, object>

这个想法是这个对象在基于插件的架构中共享,因此一个类型(可以驻留在一个插件.dll中)被用作一个键,以防止插件之间的冲突。此类型还用于存储有关该字段的元数据(例如该字段的描述或该字段的实际类型)。

目前,插件需要使用与此类似的代码设置此对象上的字段值:

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

唯一的问题是这不是类型安全的 - 即使类型UsernameField知道它所期望的值的确切类型(例如int或在这种情况下{ {1}}),此处提供的值只是作为对象键入。我想使用泛型来设置/获取属性类型安全,但我不确定如何。到目前为止,我提出的最好的是:

string

这是类型安全的,但这意味着我的每个字段类型(例如// 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); )都具有几乎相同的静态UsernameField方法。

如何在不使用每种字段类型的方法的情况下,以这种方式对值进行类型安全访问?

顺便说一下,使用Set这样的关键是一个好主意,还是有一些我还不知道的隐藏陷阱?

3 个答案:

答案 0 :(得分:2)

定义所有插件必须实现的接口:

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

然后使用Dictionary<Type, IPlugIn>

通常,接口在单独的dll中声明(例如“MyCompany.MyProject.PlugIns.Contracts.dll”)。


编辑:好的,我想我现在知道你的意思了。

诀窍是在FieldUsernameField之间使用通用Set方法创建泛型类。 Field不是通用的,这使得所有字段类型都可以分配给它。如果它被声明为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

}

由于我们需要在GetType方法中调用Set,因此我们无法将其声明为static。因此,我使用了单例模式。

现在,我们可以以类型安全的方式设置字段:

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

附加1:

由于现在字段是单例,您可以使用字段作为字典的键而不是字体。

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

Set将成为:

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

附加2:

你也可以像这样定义DynamicObject

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

现在您可以设置如下值:

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

这是类型安全的,看起来更自然。 Set中的GenericField方法现已过时。

答案 1 :(得分:1)

我可能只是忽略了问题的重点,但如果目标是减少实现类型安全所需的代码重复量,那么为什么不根据字段类型而不是字段本身来定义访问者助手:< / p>

public abstract class StringField<T> : Field
{
    public static void Set(DynamicObject obj, string value)
    {
        obj[typeof(T)] = someValue;
    }
}

public class UsernameField : StringField<UsernameField> { }

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

编辑:

或者你可以在没有Singleton的情况下使用Oliver's solution的轻微变化:

public abstract class GenericField<T, U> : Field
{
    public static void Set(DynamicObject obj, T value)
    {
        obj[typeof(U)] = value;
    }
}

public class UsernameField : GenericField<string, UsernameField> { }

答案 2 :(得分:1)

Fields不应该知道DynamicObject来获取和设置值。您可以让DyanmicObject处理值的获取和设置,但我认为更好的方法是将DynamicObject视为Field个实例的集合,并且每个字段都有自己的值。像这样:

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

然后用法是:

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