هل لدى C# طريقة لإعطائي قاموسًا غير قابل للتغيير؟

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

سؤال

هل هناك أي شيء مدمج في مكتبات C# الأساسية يمكن أن يمنحني قاموسًا غير قابل للتغيير؟

شيء على غرار جافا:

Collections.unmodifiableMap(myMap);

وفقط للتوضيح، أنا لا أتطلع إلى منع تغيير المفاتيح/القيم نفسها، فقط بنية القاموس.أريد شيئًا يفشل بسرعة وبصوت عالٍ إذا تم استدعاء أي من أساليب تحويل IDictionary (Add, Remove, Clear).

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

المحلول

لا، ولكن الغلاف تافه إلى حد ما:

public class ReadOnlyDictionary<TKey, TValue> : IDictionary<TKey, TValue>
{
    IDictionary<TKey, TValue> _dict;

    public ReadOnlyDictionary(IDictionary<TKey, TValue> backingDict)
    {
        _dict = backingDict;
    }

    public void Add(TKey key, TValue value)
    {
        throw new InvalidOperationException();
    }

    public bool ContainsKey(TKey key)
    {
        return _dict.ContainsKey(key);
    }

    public ICollection<TKey> Keys
    {
        get { return _dict.Keys; }
    }

    public bool Remove(TKey key)
    {
        throw new InvalidOperationException();
    }

    public bool TryGetValue(TKey key, out TValue value)
    {
        return _dict.TryGetValue(key, out value);
    }

    public ICollection<TValue> Values
    {
        get { return _dict.Values; }
    }

    public TValue this[TKey key]
    {
        get { return _dict[key]; }
        set { throw new InvalidOperationException(); }
    }

    public void Add(KeyValuePair<TKey, TValue> item)
    {
        throw new InvalidOperationException();
    }

    public void Clear()
    {
        throw new InvalidOperationException();
    }

    public bool Contains(KeyValuePair<TKey, TValue> item)
    {
        return _dict.Contains(item);
    }

    public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
    {
        _dict.CopyTo(array, arrayIndex);
    }

    public int Count
    {
        get { return _dict.Count; }
    }

    public bool IsReadOnly
    {
        get { return true; }
    }

    public bool Remove(KeyValuePair<TKey, TValue> item)
    {
        throw new InvalidOperationException();
    }

    public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
    {
        return _dict.GetEnumerator();
    }

    System.Collections.IEnumerator 
           System.Collections.IEnumerable.GetEnumerator()
    {
        return ((System.Collections.IEnumerable)_dict).GetEnumerator();
    }
}

من الواضح أنه يمكنك تغيير أداة ضبط this[] أعلاه إذا كنت تريد السماح بتعديل القيم.

نصائح أخرى

مع إصدار .NET 4.5، هناك جديد ReadOnlyDictionary فصل.يمكنك ببساطة تمرير IDictionary إلى المنشئ لإنشاء القاموس غير القابل للتغيير.

هنا هي طريقة تمديد مفيدة يمكن استخدامها لتبسيط عملية إنشاء قاموس للقراءة فقط.

أنا لا أعتقد ذلك.هناك طريقة لإنشاء قائمة للقراءة فقط ومجموعة للقراءة فقط، لكنني لا أعتقد أن هناك قاموسًا مدمجًا للقراءة فقط.يحتوي System.ServiceModel على تطبيق ReadOnlyDictinoary، ولكنه داخلي.ربما لن يكون من الصعب جدًا نسخه باستخدام Reflector، أو ببساطة إنشاء نسخة خاصة بك من الصفر.إنه يغلف بشكل أساسي قاموسًا ويرمي عند استدعاء المتحول.

إضافة إلى إجابة dbkk, ، أردت أن أكون قادرًا على استخدام أداة تهيئة الكائن عند إنشاء ReadOnlyDictionary لأول مرة.لقد قمت بالتعديلات التالية:

private readonly int _finalCount;

/// <summary>
/// Takes a count of how many key-value pairs should be allowed.
/// Dictionary can be modified to add up to that many pairs, but no
/// pair can be modified or removed after it is added.  Intended to be
/// used with an object initializer.
/// </summary>
/// <param name="count"></param>
public ReadOnlyDictionary(int count)
{
    _dict = new SortedDictionary<TKey, TValue>();
    _finalCount = count;
}

/// <summary>
/// To allow object initializers, this will allow the dictionary to be
/// added onto up to a certain number, specifically the count set in
/// one of the constructors.
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
public void Add(TKey key, TValue value)
{
    if (_dict.Keys.Count < _finalCount)
    {
        _dict.Add(key, value);
    }
    else
    {
        throw new InvalidOperationException(
            "Cannot add pair <" + key + ", " + value + "> because " +
            "maximum final count " + _finalCount + " has been reached"
        );
    }
}

الآن يمكنني استخدام الفصل مثل ذلك:

ReadOnlyDictionary<string, string> Fields =
    new ReadOnlyDictionary<string, string>(2)
        {
            {"hey", "now"},
            {"you", "there"}
        };

المصدر المفتوح مجموعات الطاقة تشتمل المكتبة على غلاف قاموس للقراءة فقط (بالإضافة إلى أغلفة للقراءة فقط لكل شيء آخر تقريبًا)، يمكن الوصول إليها عبر برنامج ثابت ReadOnly() الطريقة على Algorithms فصل.

قد يكون أحد الحلول هو طرح قائمة جديدة من KeyValuePair من القاموس للحفاظ على النسخة الأصلية دون تعديل.

var dict = new Dictionary<string, string>();

dict.Add("Hello", "World");
dict.Add("The", "Quick");
dict.Add("Brown", "Fox");

var dictCopy = dict.Select(
    item => new KeyValuePair<string, string>(item.Key, item.Value));

// returns dictCopy;

بهذه الطريقة لن يتم تعديل القاموس الأصلي.

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

لقد وجدت تطبيقًا غير قابل للتغيير (وليس للقراءة فقط) لتطبيق AVLTree لـ C# هنا.

تتمتع شجرة AVL بتكلفة لوغاريتمية (ليست ثابتة) لكل عملية، ولكنها تظل سريعة.

http://csharpfeeds.com/post/7512/Immutability_in_Csharp_Part_Nine_Academic_Plus_my_AVL_tree_implementation.aspx

منذ Linq، هناك واجهة عامة اتطلع.اقرأ المزيد في MSDN.

لذلك، للحصول على قاموس غير قابل للتغيير، يمكنك الاتصال بما يلي:

using System.Linq;
// (...)
var dictionary = new Dictionary<string, object>();
// (...)
var read_only = dictionary.ToLookup(kv => kv.Key, kv => kv.Value);

يمكنك تجربة شيء مثل هذا:

private readonly Dictionary<string, string> _someDictionary;

public IEnumerable<KeyValuePair<string, string>> SomeDictionary
{
    get { return _someDictionary; }
}

سيؤدي هذا إلى إزالة مشكلة قابلية التغيير لصالح جعل المتصل لديك إما يقوم بتحويله إلى القاموس الخاص به:

foo.SomeDictionary.ToDictionary(kvp => kvp.Key);

...أو استخدم عملية مقارنة على المفتاح بدلاً من البحث عن الفهرس، على سبيل المثال:

foo.SomeDictionary.First(kvp => kvp.Key == "SomeKey");

بشكل عام، من الأفضل عدم تمرير أي قواميس في المقام الأول (إذا لم تكن مضطرًا لذلك).

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

public interface IMyDomainObjectDictionary 
{
    IMyDomainObject GetMyDomainObject(string key);
}

internal class MyDomainObjectDictionary : IMyDomainObjectDictionary 
{
    public IDictionary<string, IMyDomainObject> _myDictionary { get; set; }
    public IMyDomainObject GetMyDomainObject(string key)         {.._myDictionary .TryGetValue..etc...};
}

هناك أيضًا بديل آخر كما وصفته في:

http://www.softwarerockstar.com/2010/10/readonlydictionary-tkey-tvalue/

إنها في الأساس فئة فرعية من ReadOnlyCollection>، والتي تنجز العمل بطريقة أكثر أناقة.أنيق بمعنى أنه يتمتع بدعم وقت الترجمة لجعل القاموس للقراءة فقط بدلاً من طرح استثناءات من الأساليب التي تعدل العناصر الموجودة بداخله.

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