C # как установить скрытое свойство с помощью «нового» свойства?

#c# #generics #tree

#c# #общие сведения #дерево

Вопрос:

У меня есть класс

 public class MenuItem
{
    // This class have a lot of fixed properties that I can't change
    public string Foo { get; set; } 

    // Child items used to create a tree structure
    public IList<MenuItem> Items { get; set; }
}
  

И теперь я хочу создать новый класс, который наследует от MenuItem этого, будет содержать больше данных, которые мне нужны.

 public class MenuItemData<T> : MenuItem
{
    public T Data { get; set; }

    public new IList<MenuItemData<T>> Items { get; set; }
}
  

Но чтобы иметь древовидную структуру, мне нужно как-то «изменить» тип Items , чтобы он был моим новым MenuItemData<T> , поэтому я использовал new .

Но тогда проблема в том, что когда я заполняю эту древовидную структуру MenuItemData<T> , она устанавливает только значение IList<MenuItemData<T>> Items , а не IList<MenuItem> Items то, которое будет использоваться для создания древовидной структуры.

Итак, мне нужно как-то настроить оба Items для его работы … но я не знаю, как это сделать.

То, что я пытался сделать, это установить древовидную структуру с помощью, IList<MenuItemData<T>> Items а затем пройти по дереву и установить скрытое свойство, но я также не знаю, как я могу получить «новое» свойство и установить его значение для скрытого свойства («новое» свойство наследуется от скрытого свойства, поэтомуЯ должен иметь возможность устанавливать значение от одного к другому).

Пожалуйста, обратите внимание, что:

  • Я не могу изменить MenuItem класс.
  • Если есть решение без new , оно тоже будет в порядке
  • Если вопрос сбивает с толку, пожалуйста, дайте мне знать, и я могу привести несколько примеров того, как я обхожу дерево.

Комментарии:

1. Для начала вы можете переименовать Items в MenuItemData классе во что-то другое, тогда проблема станет понятнее (поскольку это отдельное свойство и не связано с родительским Items свойством).

2. @Evk Items Вход MenuItemData тот же Items MenuItem , единственное отличие в том, что я хочу добавить к нему некоторые новые данные, чтобы внести некоторую логику, но для отображения древовидной структуры оба свойства представляют одно и то же

3. IList<T> вы можете посмотреть на инвариантный интерфейс IEnumerable<T> , который ковариантен в отношении T параметра типа

4. @PavelAnikhouski Я понимаю, что вы имеете в виду, но я понятия не имею, что делать с этой информацией

5. Вы можете сделать это да, но обратите внимание, что теоретически все еще возможно добавить элемент, к MenuItem.Items которому его нет MenuItemData , тогда ваше приведение завершится неудачей. Если вас это устраивает, тогда лучше продолжайте использовать new .. Items свойство для дочернего класса, иначе его использование будет еще более запутанным (не уверен, куда добавлять элемент: в элементы или в элементы данных).

Ответ №1:

Вы можете получить доступ к скрытому свойству с приведением к типу со скрытым свойством :

 static void SetItems<T>(MenuItemData<T> menu, IList<MenuItemData<T>> items)
{
    menu.Items = items;
    ((MenuItem)menu).Items = items.Cast<MenuItem>().ToList();
}
  

Кроме того, вы можете использовать ключевое base слово для доступа к скрытому свойству из производного класса. Кроме того, класс MenuItemDataList<T> реализует IList<MenuItem> и IList<MenuItemData<T>> :

 public class MenuItemData<T> : MenuItem
{
    public T Data { get; set; }
    public new IList<MenuItemData<T>> Items
    {
        get => (MenuItemDataList<T>)base.Items;
        set => base.Items = new MenuItemDataList<T>(value);
    }
}

public class MenuItemDataList<T> : List<MenuItemData<T>>, IList<MenuItem>
{
    public MenuItemDataList(IList<MenuItemData<T>> items) : base(items)
    {}

    MenuItem IList<MenuItem>.this[int index]
    {
        get => this[index];
        set => this[index] = (MenuItemData<T>)value;
    }
    bool ICollection<MenuItem>.IsReadOnly => false;
    void ICollection<MenuItem>.Add(MenuItem item) => Add((MenuItemData<T>)item);
    bool ICollection<MenuItem>.Contains(MenuItem item) => item is MenuItemData<T> data amp;amp; Contains(data);
    void ICollection<MenuItem>.CopyTo(MenuItem[] array, int arrayIndex) => throw new NotImplementedException();
    IEnumerator<MenuItem> IEnumerable<MenuItem>.GetEnumerator() => GetEnumerator();
    int IList<MenuItem>.IndexOf(MenuItem item) => item is MenuItemData<T> data ? IndexOf((MenuItemData<T>)item) : -1;
    void IList<MenuItem>.Insert(int index, MenuItem item) => Insert(index, (MenuItemData<T>)item);
    bool ICollection<MenuItem>.Remove(MenuItem item) => Remove((MenuItemData<T>)item);
}
  

Комментарии:

1. Но если я это сделаю Items.Add , оно не установит значение в базовом классе (т. Е. Не вызовет установщик)

2. Да, решением может быть класс MenuItemDataList<T> , который наследует List<MenuItem> и реализует IList<MenuItemDataList<T> .

3. Смотрите мое редактирование, Items.Add будет работать с новым решением.

4. Первый подход — это именно то, что мне нужно, я не знал, что вы можете привести присваиваемую переменную. Второй подход, который я не тестировал, но тоже выглядит нормально, но в моем случае первый решил проблему. Спасибо!