Создание коллекционных предметов только для чтения

У меня есть хозяйствующие субъекты, как показано ниже,

class Class1
{
   List<Class2> classes = new List<Class2>();

   public IEnumerable<Class2> Classes { get { return classes.AsEnumrable(); }

   public void AddClass(Class2 cls)
   {
       classes.Add(cls);
   }
}

class Class2
{
    public string Property { get; set; }
}

Моя бизнес-логика требует, чтобы один раз Class2 Экземпляр добавляется с помощью AddClass метод к вершине Classes список в Class1никто не должен иметь возможности редактировать свойства Class2 экземпляры, добавленные ранее в список, можно редактировать только последний элемент в списке. Как мне это сделать?

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

5 ответов

Почему вы выставляете IReadOnlyCollection, После того, как вы выставили объекты, сами объекты должны быть неизменными.

Почему бы просто не выставить единственный объект, который вы хотите выставить?

   private IEnumerable<Class2> Classes { get { return classes; }

   public Class2 Class2Instance { get { return classes.Last(); } }

Задача контейнера не диктовать поведение его элементов. Контейнер - это просто объект, содержащий другие объекты. IReadOnlyList это контейнер, элементы которого нельзя изменить. Но предметы внутри него - просто Class2 экземпляры - контейнер ничего не может сделать, чтобы предотвратить их редактирование.

Учти это:

myReadOnlyCollection[0].Property = "blah";
var firstItem = myReadOnlyCollection[0];
firstItem.Property = "blah";

Должно ли это быть законным в вашем сценарии? Какая разница между этими двумя? firstItem это просто пример Class2 он понятия не имеет, что когда-то был в коллекции только для чтения.

Что вам нужно для Class2 Сам по себе быть неизменным. Это зависит от класса, а не от контейнера. Читайте об неизменности, которая является важной концепцией для понимания и реализации Class2 соответственно. Если вам нужен регулярный, изменчивый Class2 изменить свое поведение, возможно, добавить ToImmutable() метод, который возвращает другой элемент, без установщика.

Я вижу только три варианта. Одним из них является изменение Class2, чтобы сделать его блокируемым, а затем заблокировать его, как только он будет добавлен в ваш список...

class Class1 {
   List<Class2> classes = new List<Class2>();

   public IEnumerable<Class2> Classes {
      get { return classes.AsEnumrable();
   }

   public void AddClass(Class2 cls) {
      cls.Lock();
      classes.Add(cls);
   }
}

class Class2 {
    private string _property;
    private bool _locked;

    public string Property {
       get { return _property; }
       set {
          if(_locked) throw new AccessViolationException();
          _property = value;
       }
    }

    public void Lock() {
       _locked = true;
    }
}

Другой вариант - возвращать только значения объектов списка, а не сами объекты...

class Class1 {
   List<Class2> classes = new List<Class2>();

   public IEnumerable<string> Values {
      get { return classes.Select(cls => cls.Property); }
   }

   public void AddClass(Class2 cls) {
      classes.Add(cls);
   }
}

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

class Class2ReadOnly {
   private Class2 _master;

   public Class2ReadOnly(Class2 master) {
      _master = master;
   }

   public string Property {
      get { return _master.Property; }
   }
}

class Class1 {
   List<Class2ReadOnly> classes = new List<Class2ReadOnly>();

   public IEnumerable<Class2ReadOnly> Classes {
      get { return classes.AsEnumerable(); }
   }

   public void AddClass(Class2 cls) {
      classes.Add(new Class2ReadOnly(cls));
   }
}

Я знаю, что это старая проблема, однако сегодня я столкнулся с той же проблемой.

Справочная информация: я хочу хранить данные в своем приложении, например, пользователи могут устанавливать свои пользовательские объекты в проекте, а механизм отмены-повтора должен быть адаптирован для обработки пакетного хранения данных.

Мой подход: я создал несколько интерфейсов и сделал оболочку для коллекции, которую не хочу, чтобы пользователи изменяли.

      public class Repository : IRepository
{
    // These items still can be changed via Items[0].CustomProperty = "asd"
    public readonly List<CustomItem> Items { get; }

    private readonly List<CustomItem> m_Originaltems;

    //However, when I create RepositoryObject, I create a shadow copy of the Items collection

    public Repository(List<CustomItem> items)
    {
        items.ForEach((item) =>
        {
            // By cloning an item you can make sure that any change to it can be easily discarded
            Items.Add((CustomItem)item.Clone());
        });

        // As a private field we can manage the original collection without taking into account any unintended modification
        m_OriginalItems = items;
    }
    
    // Adding a new item works with the original collection
    public void AddItem(CustomItem item)
    {
        m_OriginalItems.Add(item);
    }

    // Of course you have to implement all the necessary methods you want to use (e.g. Replace, Remove, Insert and so on)
}

Плюсы: используя этот подход, вы просто оборачиваете коллекцию в пользовательский объект. Единственное, чего вы ожидаете от пользователя, это иметьреализован интерфейс. Если вы хотите, вы также можете сделать свою оболочку универсальной и указать ограничение, напримерwhere T : ICloneable

Минусы: если вы добавите новые элементы, вы не узнаете о них, проверив свойство Items. Обходной путь можно решить, создавая копию коллекции всякий раз, когданазывается. Это зависит от вас и ваших требований. Я имел в виду что-то вроде этого:

      public class Repository : IRepository
{
    public List<CustomItem> Items => m_OriginalItems.Select(item => (CustomItem)item.Clone()).ToList();

    private readonly List<CustomItem> m_Originaltems;

    public Repository(List<CustomItem> items)
    {
        m_OriginalItems = items;
    }
    
    public void AddItem(CustomItem item)
    {
        m_OriginalItems.Add(item);
    }

    // Of course you still have to implement all the necessary methods you want to use (e.g. Replace, Count, and so on)
}

Как уже говорилось, задача сбора коллекций - диктовать, обращаетесь ли вы (и как) к его элементам. Я вижу способы обойти это:

Исключения и ссылки:

Измените Class2, чтобы он мог ссылаться на Class1. Если ссылка установлена, бросить исключения на всех установщиков. Измените Class1.AddClass, чтобы установить это свойство.

Более мягкой версией этого будет свойство "только для чтения" в Class2, которое должен проверять весь другой код.

Readonly свойства и конструкторы:

Просто всегда давайте Class2 только для чтения свойства (закрытый набор). Если вы хотите определить значения свойств, вы должны сделать это в конструкторе (который имеет правильные аргументы). Этот шаблон интенсивно используется классами Exception.

Наследование Shenanigans:

Создайте несколько версий Class2 в цепочке наследования, чтобы этот Class2Writebale мог быть приведен к Class2ReadOnly.

Примите неверный Y:

Возможно, вы столкнулись с проблемой XY: https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem Если это так, сделайте шаг назад, чтобы исправить это.

Другие вопросы по тегам