Изменение типа свойства в классе, который реализует интерфейс со свойством типа объекта - PullRequest
6 голосов
/ 18 апреля 2010

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

interface IControl
    string Id
    object Value

class Label : IControl
    string Id
    string Value

class Repeater : IControl
    string Id
    List<IControl> Value

Теперь вы сразу увидите странную часть в классе Repeater со свойством Value. Я надеялся, что наличие типа Value в качестве объекта в интерфейсе позволит мне гибко расширять элементы управления по мере продвижения вперед. Компилятору это не нравится, и я думаю, что на это есть веские причины.

Итог: я пытаюсь заставить все классы управления реализовывать один и тот же интерфейс, но иметь разные типы для свойства Value.

У кого-нибудь есть предложения, как этого добиться?

Примечание. Пожалуйста, не предлагайте такие вещи, как использование Spark View Engine для создания шаблонов. Есть причина, по которой я создаю для себя дополнительную работу.

Ответы [ 3 ]

9 голосов
/ 18 апреля 2010

Обычно Repeater реализует что-то другое, например IItemsControl.

РЕДАКТИРОВАТЬ 1

(удалено для краткости)

РЕДАКТИРОВАТЬ 2

Хорошо, вы всегда можете использовать явную реализацию интерфейса, конечно:

interface IControl
{
    string Id { get; set; }
    object Value { get; set; }
}

class Label : IControl
{
    public string Id { get; set; }
    public string Value { get; set; }

    object IControl.Value
    {
        get { return this.Value; }
        set { this.Value = (string)value; }
    }
}

class Repeater : IControl
{
    public string Id { get; set; }
    public IList<IControl> Value { get; set; }

    object IControl.Value
    {
        get { return this.Value; }
        set { this.Value = (IList<IControl>)value; }
    }
}
3 голосов
/ 19 апреля 2010

Вы также можете использовать дженерики:

interface IControl<T> 
{
    string ID{get;set;}
    T Value{get;set;}
}

class SomeControl : IControl<string>
{
    public string ID{get;set}
    public string Value{get;set;}
}

class SomeOtherControl : IControl<int>
{
    public string ID{get;set}
    public int Value{get;set;}
}

Мне нравится это лучше, чем явная идея интерфейса, если нужно изменить только одно возвращаемое значение. Однако, я думаю, что если бы у вас было несколько свойств, каждое из которых возвращало бы свой тип, вы бы не хотели иметь IControl. По крайней мере, я бы не стал. В этом случае я бы порекомендовал явные интерфейсы.

Конечно, это не сработает, если у вас не будет доступа к источнику IControl.

Редактировать: опечатка. Fixed

1 голос
/ 19 апреля 2010

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

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

Однако, если вы сделаете Value возвращаемым функцией, то это сработает, но вам нужно проверить тип возвращаемого значения, потому что возвращаемые типы должны совпадать для функции, иначе вы получите ошибку, что функция интерфейса не была реализована.

    interface IControl
    {
        object Value();
    }
    class A : IControl
    {
        string m_value = string.Empty;
        public object Value() { return m_value; }
    };
    class B : IControl
    {
        List<IControl> m_value = new List<IControl>();
        public object Value() { return m_value; }
    };
    ....
    object o = new B().Value();
    if (o is List<IControl>)
        MessageBox.Show("List");

[Update]
Вы должны быть осторожны, если явно определяете тело свойств. Наличие одного имени для двух свойств было бы опасно, если реализация не выполнена тщательно.

Эти два свойства, если они содержат разные определения, были бы необъяснимы для конечного использования интерфейса и классов.

        public IList<IControl> Value
        object IControl.Value

См. Этот пример:

    ...
    class Repeater : IControl
    {
        List<IControl> m_Value = new List<IControl>();
        public IList<IControl> Value
        {
            get { return this.m_Value; }
            set { this.m_Value = (IList<IControl>)value; }
        }
        object IControl.Value
        {
            get
            {
                return this.m_Value;
            }
            set
            {
                this.m_Value = new List<IControl>();
                this.m_Value.Add(new Label());
                this.m_Value.AddRange((List<IControl>)value);
            }
        }
    }
    ...
    Repeater b = new Repeater();
    IControl i = b;
    List<IControl> list = new List<IControl>();
    list.Add(new Repeater());
    i.Value = list;

Вы можете заметить, что контейнер списка в Repeater будет иметь разные значения при добавлении данных через IControl (из-за явного определения IContainer.Value).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...