在 C# 中控制对内部集合的访问 - 需要模式

发布于 2024-07-06 04:46:43 字数 212 浏览 14 评论 0原文

这有点难以解释,我希望我的英语足够:

我有一个类“A”,它应该维护类“B”的对象列表(如私有列表)。 “A”类消费者应该能够将项目添加到列表中。 将项目添加到列表后,消费者不应该能够再次修改它们,更不用说他不应该能够修改列表本身(添加或删除项目)。 但他应该能够枚举列表中的项目并获取它们的值。 有一个模式吗? 你会怎么做?

如果问题不够清楚,请告诉我。

This is kind of hard to explain, I hope my English is sufficient:

I have a class "A" which should maintain a list of objects of class "B" (like a private List). A consumer of class "A" should be able to add items to the list. After the items are added to the list, the consumer should not be able to modify them again, left alone that he should not be able to temper with the list itself (add or remove items). But he should be able to enumerate the items in the list and get their values. Is there a pattern for it? How would you do that?

If the question is not clear enough, please let me know.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

难忘№最初的完美 2024-07-13 04:46:44

为了防止编辑列表或其项目,您必须使它们不可变,这意味着您必须在每次请求时返回元素的新实例。

请参阅 Eric Lippert 的优秀系列“C# 中的不变性”:http: //blogs.msdn.com/ericlippert/archive/tags/Immutability/C_2300_/default.aspx(您必须向下滚动一点)

To prevent editing the list or its items you have to make them immutable, which means you have to return a new instance of an element on every request.

See Eric Lippert's excellent series of "Immutability in C#": http://blogs.msdn.com/ericlippert/archive/tags/Immutability/C_2300_/default.aspx (you have to scroll down a bit)

蓦然回首 2024-07-13 04:46:44

正如许多答案所示,有很多方法可以使集合本身不可变。

需要付出更多努力来保持集合成员的不可变性。 一种可能性是使用外观/代理(抱歉不够简洁):

class B
{
    public B(int data) 
    { 
        this.data = data; 
    }

    public int data
    {
        get { return privateData; }
        set { privateData = value; }
    }

    private int privateData;
}

class ProxyB
{
    public ProxyB(B b)   
    { 
        actual = b; 
    }

    public int data
    {
        get { return actual.data; }
    }

    private B actual;
}

class A : IEnumerable<ProxyB>
{
    private List<B> bList = new List<B>();

    class ProxyEnumerator : IEnumerator<ProxyB>
    {
        private IEnumerator<B> b_enum;

        public ProxyEnumerator(IEnumerator<B> benum)
        {
            b_enum = benum;
        }

        public bool MoveNext()
        {
            return b_enum.MoveNext();
        }

        public ProxyB Current
        {
            get { return new ProxyB(b_enum.Current); }
        }

        Object IEnumerator.Current
        {
            get { return this.Current; }
        }

        public void Reset()
        {
            b_enum.Reset();
        }

        public void Dispose()
        {
            b_enum.Dispose();
        }
    }

    public void AddB(B b) { bList.Add(b); }

    public IEnumerator<ProxyB> GetEnumerator()
    {
        return new ProxyEnumerator(bList.GetEnumerator());
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
}

此解决方案的缺点是调用者将迭代 ProxyB 对象的集合,而不是他们添加的 B 对象。

As many of these answers show, there are many ways to make the collection itself immutable.

It takes more effort to keep the members of the collection immutable. One possibility is to use a facade/proxy (sorry for the lack of brevity):

class B
{
    public B(int data) 
    { 
        this.data = data; 
    }

    public int data
    {
        get { return privateData; }
        set { privateData = value; }
    }

    private int privateData;
}

class ProxyB
{
    public ProxyB(B b)   
    { 
        actual = b; 
    }

    public int data
    {
        get { return actual.data; }
    }

    private B actual;
}

class A : IEnumerable<ProxyB>
{
    private List<B> bList = new List<B>();

    class ProxyEnumerator : IEnumerator<ProxyB>
    {
        private IEnumerator<B> b_enum;

        public ProxyEnumerator(IEnumerator<B> benum)
        {
            b_enum = benum;
        }

        public bool MoveNext()
        {
            return b_enum.MoveNext();
        }

        public ProxyB Current
        {
            get { return new ProxyB(b_enum.Current); }
        }

        Object IEnumerator.Current
        {
            get { return this.Current; }
        }

        public void Reset()
        {
            b_enum.Reset();
        }

        public void Dispose()
        {
            b_enum.Dispose();
        }
    }

    public void AddB(B b) { bList.Add(b); }

    public IEnumerator<ProxyB> GetEnumerator()
    {
        return new ProxyEnumerator(bList.GetEnumerator());
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
}

The downside of this solution is that the caller will be iterating over a collection of ProxyB objects, rather than the B objects they added.

清君侧 2024-07-13 04:46:44

目前尚不清楚您是否还需要 B 实例本身在添加到列表后保持不变。 您可以在这里玩点小把戏,为 B 使用只读接口,并仅通过列表公开这些接口。

internal class B : IB
{
    private string someData;

    public string SomeData
    {
        get { return someData; }
        set { someData = value; }
    }
}

public interface IB
{
    string SomeData { get; }
}

It wasn't clear whether you also needed the B instances themselves to be immutable once added to the list. You can play a trick here by using a read-only interface for B, and only exposing these through the list.

internal class B : IB
{
    private string someData;

    public string SomeData
    {
        get { return someData; }
        set { someData = value; }
    }
}

public interface IB
{
    string SomeData { get; }
}
千纸鹤 2024-07-13 04:46:44

编辑:添加了对版本上下文的支持。 调用者只能在版本上下文中添加元素。 您还可以强制要求在实例的生命周期内只能创建一个版本上下文。


使用封装,您可以定义任何策略集来访问内部私有成员。 以下示例是您需求的基本实现:

namespace ConsoleApplication2
{
    using System;
    using System.Collections.Generic;
    using System.Collections;

    class B
    {
    }

    interface IEditable
    {
        void StartEdit();
        void StopEdit();
    }

    class EditContext<T> : IDisposable where T : IEditable
    {
        private T parent;

        public EditContext(T parent)
        {
            parent.StartEdit();
            this.parent = parent;
        }

        public void Dispose()
        {
            this.parent.StopEdit();
        }
    }

    class A : IEnumerable<B>, IEditable
    {
        private List<B> _myList = new List<B>();
        private bool editable;

        public void Add(B o)
        {
            if (!editable)
            {
                throw new NotSupportedException();
            }
            _myList.Add(o);
        }

        public EditContext<A> ForEdition()
        {
            return new EditContext<A>(this);
        }

        public IEnumerator<B> GetEnumerator()
        {
            return _myList.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        public void StartEdit()
        {
            this.editable = true;
        }

        public void StopEdit()
        {
            this.editable = false;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            A a = new A();
            using (EditContext<A> edit = a.ForEdition())
            {
                a.Add(new B());
                a.Add(new B());
            }

            foreach (B o in a)
            {
                Console.WriteLine(o.GetType().ToString());
            }

            a.Add(new B());

            Console.ReadLine();
        }
    }
}

EDIT: Added support for edition contexts. Caller can only add elements inside an edition context. You can aditionally enforce that only one edition context can be created for the lifetime of the instance.


Using encapsulation you can define any set of policies to access the inner private member. The following example is a basic implementation of your requirements:

namespace ConsoleApplication2
{
    using System;
    using System.Collections.Generic;
    using System.Collections;

    class B
    {
    }

    interface IEditable
    {
        void StartEdit();
        void StopEdit();
    }

    class EditContext<T> : IDisposable where T : IEditable
    {
        private T parent;

        public EditContext(T parent)
        {
            parent.StartEdit();
            this.parent = parent;
        }

        public void Dispose()
        {
            this.parent.StopEdit();
        }
    }

    class A : IEnumerable<B>, IEditable
    {
        private List<B> _myList = new List<B>();
        private bool editable;

        public void Add(B o)
        {
            if (!editable)
            {
                throw new NotSupportedException();
            }
            _myList.Add(o);
        }

        public EditContext<A> ForEdition()
        {
            return new EditContext<A>(this);
        }

        public IEnumerator<B> GetEnumerator()
        {
            return _myList.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        public void StartEdit()
        {
            this.editable = true;
        }

        public void StopEdit()
        {
            this.editable = false;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            A a = new A();
            using (EditContext<A> edit = a.ForEdition())
            {
                a.Add(new B());
                a.Add(new B());
            }

            foreach (B o in a)
            {
                Console.WriteLine(o.GetType().ToString());
            }

            a.Add(new B());

            Console.ReadLine();
        }
    }
}
世界和平 2024-07-13 04:46:44

您基本上希望避免泄露对 B 类项目的引用。 这就是为什么您应该复制这些项目。

我认为这可以通过 List 对象的 ToArray() 方法来解决。 如果您想防止更改,则需要创建列表的深层副本。

一般来说:大多数时候,不值得通过复制来强制执行良好行为,尤其是当您还编写消费者时。

You basically want to avoid to give away references to the class B items. That's why you should do a copy of the items.

I think this can be solved with the ToArray() method of a List object. You need to create a deep-copy of the list if you want to prevent changes.

Generally speaking: most of the times it is not worthwhile to do a copy to enforce good behaviour, especially when you also write the consumer.

只是偏爱你 2024-07-13 04:46:44
public class MyList<T> : IEnumerable<T>{

    public MyList(IEnumerable<T> source){
        data.AddRange(source);
    }

    public IEnumerator<T> GetEnumerator(){
        return data.Enumerator();
    }

    private List<T> data = new List<T>();
}

缺点是消费者可以修改从枚举器获取的项目,解决方案是对私有 List进行深度复制。

public class MyList<T> : IEnumerable<T>{

    public MyList(IEnumerable<T> source){
        data.AddRange(source);
    }

    public IEnumerator<T> GetEnumerator(){
        return data.Enumerator();
    }

    private List<T> data = new List<T>();
}

The downside is that a consumer can modify the items it gets from the Enumerator, a solution is to make deepcopy of the private List<T>.

脱离于你 2024-07-13 04:46:44

我能想到的最简单的方法是返回 只读版本如果不再允许编辑,则为基础集合。

public IList ListOfB
{
    get 
    {
        if (_readOnlyMode) 
            return listOfB.AsReadOnly(); // also use ArrayList.ReadOnly(listOfB);
        else
            return listOfB;
    }
}

但就我个人而言,我不会向客户端公开底层列表,而只是提供添加、删除和枚举 B 实例的方法。

The simplest that I can think of is return a readonly version of the underlying collection if editing is no longer allowed.

public IList ListOfB
{
    get 
    {
        if (_readOnlyMode) 
            return listOfB.AsReadOnly(); // also use ArrayList.ReadOnly(listOfB);
        else
            return listOfB;
    }
}

Personally though, I would not expose the underlying list to the client and just provide methods for adding, removing, and enumerating the B instances.

庆幸我还是我 2024-07-13 04:46:44

哇,对于一个简单的问题,这里有一些过于复杂的答案。

拥有一个私有 List

拥有一个 public void AddItem(T item) 方法 - 每当您决定让它停止工作时,就让它停止工作。 您可以抛出异常,也可以让它默默地失败。 取决于你在那里发生了什么。

有一个 public T[] GetItems() 方法,返回 _theList.ToArray()

Wow, there are some overly complex answers here for a simple problem.

Have a private List<T>

Have an public void AddItem(T item) method - whenever you decide to make that stop working, make it stop working. You could throw an exception or you could just make it fail silently. Depends on what you got going on over there.

Have a public T[] GetItems() method that does return _theList.ToArray()

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文