Типобезопасная установка объектов со словарем, имеющим клавишу `Type` - PullRequest
6 голосов
/ 09 января 2012

У меня есть общий словарь объектов, где ключ имеет тип Type:

public class DynamicObject : IDictionary<Type, object>

Идея состоит в том, что этот объект является общим в архитектуре на основе плагинов, и поэтому тип (который может находиться в плагине .dll) используется в качестве ключа для предотвращения конфликтов между плагинами. Этот тип также используется для хранения метаданных о поле (например, описание этого поля или фактический тип этого поля).

В настоящее время плагинам необходимо установить значение полей для этого объекта, используя код, подобный следующему:

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

Единственная проблема с этим состоит в том, что это не безопасно для типа - даже если тип UsernameField знает точный тип ожидаемого значения (например, int или в этом случае 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.

Как я могу таким образом иметь безопасный для типов доступ к значениям, не имея много почти идентичных методов для каждого из моих типов полей?

Кроме того, использование Type в качестве ключа, подобного этому, является хорошей идеей или есть скрытые подводные камни, о которых я еще не знаю?

Ответы [ 3 ]

2 голосов
/ 09 января 2012

Определите интерфейс, который должны реализовывать все ваши плагины:

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

А затем используйте Dictionary<Type, IPlugIn>.

Обычно интерфейс объявляется в отдельной dll (например, «MyCompany.MyProject.PlugIns.Contracts.dll»).


РЕДАКТИРОВАТЬ: Хорошо, я думаю, что я знаю, что вы имеете в виду сейчас.

Хитрость заключается в том, чтобы иметь общий класс между Field и UsernameField с помощью универсального метода 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 голос
/ 09 января 2012

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);
}
1 голос
/ 09 января 2012

Возможно, я просто упускаю суть вопроса, но если цель состоит в том, чтобы уменьшить количество дублирования кода, необходимого для обеспечения безопасности типов, то почему бы не определить помощники доступа, основанные на типе поля, а не на самом поле:

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

Edit:

Или вы можете использовать небольшое изменение Решение Оливера без Синглтона:

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> { }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...