التحكم في الوصول إلى مجموعة داخلية في C# - النمط مطلوب

StackOverflow https://stackoverflow.com/questions/126367

سؤال

يصعب شرح هذا الأمر، وآمل أن تكون لغتي الإنجليزية كافية:

لدي فئة "أ" والتي يجب أن تحتفظ بقائمة كائنات الفئة "ب" (مثل القائمة الخاصة).يجب أن يكون المستهلك من الفئة "أ" قادرًا على إضافة عناصر إلى القائمة.بعد إضافة العناصر إلى القائمة، يجب ألا يتمكن المستهلك من تعديلها مرة أخرى، ويترك بمفرده أنه لا ينبغي أن يكون قادرًا على التكيف مع القائمة نفسها (إضافة أو إزالة عناصر).ولكن يجب أن يكون قادرًا على تعداد العناصر الموجودة في القائمة والحصول على قيمها.هل هناك نمط لذلك؟كيف يمكنك أن تفعل ذلك؟

إذا كان السؤال غير واضح بما فيه الكفاية، واسمحوا لي أن أعرف.

هل كانت مفيدة؟

المحلول

لمنع تحرير القائمة أو عناصرها عليك القيام بها غير قابل للتغيير, ، مما يعني أنه يتعين عليك إرجاع مثيل جديد لعنصر في كل طلب.

شاهد سلسلة إريك ليبرت الممتازة "الثبات في C#": http://blogs.msdn.com/ericlippert/archive/tags/Immutability/C_2300_/default.aspx (يجب عليك التمرير للأسفل قليلاً)

نصائح أخرى

واو، هناك بعض الإجابات المعقدة للغاية هنا لمشكلة بسيطة.

لديك خاصة List<T>

أحصل على public void AddItem(T item) الطريقة - عندما تقرر إيقاف ذلك العمل، اجعله يتوقف عن العمل.يمكنك طرح استثناء أو يمكنك جعله يفشل بصمت.يعتمد على ما حدث هناك.

عند public T[] GetItems() الطريقة التي تفعل ذلك return _theList.ToArray()

كما تظهر العديد من هذه الإجابات، هناك طرق عديدة لجعل المجموعة نفسها غير قابلة للتغيير.

يستغرق الأمر المزيد من الجهد للحفاظ على أعضاء المجموعة غير قابلين للتغيير.أحد الاحتمالات هو استخدام الواجهة/الوكيل (آسف لعدم الإيجاز):

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 التي أضافوها.

يحرر: تمت إضافة دعم لسياقات الطبعة.يمكن للمتصل فقط إضافة عناصر داخل سياق الإصدار.يمكنك أيضًا فرض أنه يمكن إنشاء سياق إصدار واحد فقط طوال مدة المثيل.


باستخدام التغليف يمكنك تحديد أي مجموعة من السياسات للوصول إلى العضو الخاص الداخلي.المثال التالي هو التنفيذ الأساسي لمتطلباتك:

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

أنت تريد بشكل أساسي تجنب التخلي عن المراجع لعناصر الفئة B.لهذا السبب يجب عليك عمل نسخة من العناصر.

أعتقد أن هذا يمكن حله باستخدام طريقة ToArray() لكائن القائمة.تحتاج إلى إنشاء نسخة عميقة من القائمة إذا كنت تريد منع التغييرات.

بشكل عام:في معظم الأوقات، ليس من المفيد عمل نسخة لفرض السلوك الجيد، خاصة عندما تكتب للمستهلك أيضًا.

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

الجانب السلبي هو أنه يمكن للمستهلك تعديل العناصر التي يحصل عليها من العداد، والحل هو عمل نسخة عميقة من القائمة الخاصة<T>.

لم يكن من الواضح ما إذا كنت بحاجة أيضًا إلى أن تكون مثيلات B نفسها غير قابلة للتغيير بمجرد إضافتها إلى القائمة.يمكنك لعب خدعة هنا باستخدام واجهة للقراءة فقط لـ B، وكشفها فقط من خلال القائمة.

internal class B : IB
{
    private string someData;

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

public interface IB
{
    string SomeData { get; }
}

أبسط ما يمكنني التفكير فيه هو إرجاع أ نسخة للقراءة فقط للمجموعة الأساسية إذا لم يعد التحرير مسموحًا به.

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

على الرغم من ذلك، فأنا شخصيًا لن أعرض القائمة الأساسية للعميل وسأقدم فقط طرقًا لإضافة مثيلات B وإزالتها وتعدادها.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top