简体   繁体   English

将收藏项设为只读

[英]Making collection items read only

I have a business entities as below,我有一个业务实体如下,

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

My business logic requires that once a Class2 instance is added using the AddClass method to the top of the Classes list in Class1 , no one should be able to edit the properties of the Class2 instances added previously to the list, only the last item in the list could be edited.我的业务逻辑要求,一旦使用AddClass方法将Class2实例添加到Class1中的Classes列表的顶部,没有人应该能够编辑之前添加到列表中的Class2实例的属性,只有最后一项在列表可以编辑。 How do I do this?我该怎么做呢?

I have tried IReadOnlyList, but it appears that it is concerned with making the list structure itself uneditable without preventing the edit of its items' content.我已经尝试过 IReadOnlyList,但它似乎与使列表结构本身不可编辑而不会阻止对其项目内容的编辑有关。

It's not a container's job to dictate the behavior of its items.决定其项目的行为不是容器的工作。 A container is just that - an object that contains other objects.容器就是这样 - 一个包含其他对象的对象。 An IReadOnlyList is a container whose items cannot be modified. IReadOnlyList是一个容器,其项目不能被修改。 But the items within it are jsut Class2 instances - there's nothing that the container can do to prevent them from being edited.但是其中的项目是 jsut Class2实例 - 容器无法阻止它们被编辑。

Consider this:考虑一下:

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

Should this be legal in your scenario?在您的情况下这应该是合法的吗? What's the difference between the two?两者有什么区别? firstItem is simply an instance of Class2 that has no idea it was once inside a read-only collection. firstItem只是Class2的一个实例,它不知道它曾经位于只读集合中。

What you need is for Class2 itself to be immutable.您需要的是Class2本身是不可变的。 That's up to the class, not the container.这取决于类,而不是容器。 Read up on immutability, which is an important concept to grasp, and implement Class2 accordingly.阅读不变性,这是一个需要掌握的重要概念,并相应地实现Class2 If you need a regular, mutable Class2 to change its behavior, perhaps add a ToImmutable() method to it which returns a different item, without a setter.如果您需要一个常规的、可变的Class2来改变它的行为,也许可以向它添加一个ToImmutable()方法,该方法返回一个不同的项目,而不需要设置器。

Why are you exposing the IReadOnlyCollection .你为什么要公开IReadOnlyCollection Once you have exposed the objects, the objects themselves have to be immutable.一旦你暴露了对象,对象本身就必须是不可变的。

Why not just expose the only object that you want to expose?为什么不只公开您要公开的唯一对象?

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

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

I can only see three options.我只能看到三个选项。 One is to alter Class2 to make it lockable and then lock it once it's added to your list...一种是更改 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;
    }
}

Another option is to only return the values of the list objects instead of the objects themselves...另一种选择是只返回列表对象的值而不是对象本身......

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

In this second method, anything other than a single value and you'll need to either return a tuple.在第二种方法中,除了单个值之外的任何内容,您都需要返回一个元组。 Alternately, you could create a specific container for Class2 that exposes the values as read-only...或者,您可以为 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));
   }
}

I know it is an old problem, however I faced the same issue today.我知道这是一个老问题,但是我今天遇到了同样的问题。

Background: I want to store data in my application, eg users can set their custom objects in the project and the Undo-redo mechanism must be adapted to handle batched data storing.背景:我想在我的应用程序中存储数据,例如用户可以在项目中设置他们的自定义对象,并且必须调整 Undo-redo 机制来处理批量数据存储。

My approach: I created some interfaces, and I made a wrapper of the collection I don't want the users to modify.我的方法:我创建了一些界面,并制作了一个我不希望用户修改的集合的包装器。

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

Pros: Using this approach you basically just wraps the collection into a custom object.优点:使用这种方法,您基本上只是将集合包装到自定义对象中。 The only thing you expect from the user side is to have ICloneable interface implemented.您对用户的唯一期望就是实现ICloneable接口。 If you want, you can make your wrapper generic as well, and give a constraint like where T : ICloneable如果你愿意,你也可以让你的包装器通用,并给出一个约束,比如where T : ICloneable

Cons: If you add new items, you won't know about them by checking the Items property.缺点:如果您添加新项目,您不会通过检查 Items 属性来了解它们。 A workaround can be done by creating a copy of the collection whenever Items.get() is called.每当Items.get()时,可以通过创建集合的副本来完成解决方法。 It is up to you and your requirements.这取决于您和您的要求。 I meant something like this:我的意思是这样的:

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

As other said, it is not the collections job to dictate if (and how) you access it's elements.正如其他人所说,决定您是否(以及如何)访问它的元素并不是集合的工作。 I do see ways around this:我确实看到了解决方法:

Exceptions & references:例外和参考:

Modify Class2 so it can take a reference to Class1.修改 Class2,使其可以引用 Class1。 If the reference is set, throw excetpions on all setters.如果设置了引用,则在所有设置器上抛出异常。 Modify Class1.AddClass to set that property.修改 Class1.AddClass 以设置该属性。

A softer version of this would be a "read only" property on Class2, that all other code has to check.一个更软的版本将是 Class2 上的“只读”属性,所有其他代码都必须检查。

Readonly Properties & Constructors:只读属性和构造函数:

Just always give Class2 readonly properties (private set).总是给 Class2 只读属性(私有集)。 If you want to define the property values, you have to do that in the constructor (which has proper Arguments).如果要定义属性值,则必须在构造函数(具有适当的参数)中执行此操作。 This pattern is used heavily by the Exception classes.这种模式被 Exception 类大量使用。

Inheritance Shenanigans:传承诡计:

Make Multiple Class2 versions in an inheritance chain, so that that Class2Writebale can be cast to a Class2ReadOnly.在继承链中创建多个 Class2 版本,以便可以将 Class2Writebale 强制转换为 Class2ReadOnly。

Accept the wrong Y:接受错误的 Y:

You might have stuck yourself into a XY problem: https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem If so go a step back to fix it.您可能陷入了 XY 问题: https ://meta.stackexchange.com/questions/66377/what-is-the-xy-problem 如果是这样,请退后一步修复它。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM