มีพจนานุกรมทั่วไปแบบอ่านอย่างเดียวใน. NET หรือไม่


186

ฉันส่งคืนการอ้างอิงถึงพจนานุกรมในคุณสมบัติแบบอ่านอย่างเดียวของฉัน ฉันจะป้องกันไม่ให้ผู้บริโภคเปลี่ยนข้อมูลได้อย่างไร ถ้าเรื่องนี้เป็นความผมก็จะกลับมามันIList AsReadOnlyมีบางอย่างที่ฉันสามารถทำกับพจนานุกรมได้ไหม

Private _mydictionary As Dictionary(Of String, String)
Public ReadOnly Property MyDictionary() As Dictionary(Of String, String)
    Get
        Return _mydictionary
    End Get
End Property

4
จะต้องมีวิธีการทำบางอย่างหรืออื่น ๆ จะไม่มีคุณสมบัติ IsReadOnly ใน IDictionary ... ( msdn.microsoft.com/en-us/library/bb338949.aspx )
Powerlord

2
ผลประโยชน์เชิงแนวคิดจำนวนมากของความไม่สามารถเปลี่ยนแปลงได้สามารถทำได้โดยไม่ต้องมีการบังคับใช้รันไทม์ หากนี่เป็นโครงการส่วนตัวให้พิจารณาวิธีการที่มีระเบียบวินัยและไม่เป็นทางการ หากคุณต้องให้ข้อมูลกับผู้บริโภคคุณควรพิจารณาสำเนาลึก ๆ เมื่อคุณพิจารณาว่าคอลเลกชันที่ไม่เปลี่ยนรูปต้องการ 1) การอ้างอิงที่ไม่เปลี่ยนรูปแบบไปยังคอลเลกชัน 2) การป้องกันการกลายพันธุ์ของลำดับและ 3) การป้องกันการแก้ไขคุณสมบัติในรายการของคอลเลกชันและบางส่วนเหล่านี้อาจถูกละเมิด ไม่จริง
Sprague

27
ตั้งแต่. NET 4.5 มีSystem.Collections.ObjectModel.ReadOnlyDictionary ^ _ ^
Smartkid

2
นอกจากนี้ยังมีคอลเลกชันที่ไม่เปลี่ยนรูปแบบของ Microsoft ผ่านทาง NuGet msdn.microsoft.com/en-us/library/dn385366%28v=vs.110%29.aspx
VoteCoffee

คำตอบ:


156

นี่คือการใช้งานอย่างง่ายที่ล้อมพจนานุกรม:

public class ReadOnlyDictionary<TKey, TValue> : IDictionary<TKey, TValue>
{
    private readonly IDictionary<TKey, TValue> _dictionary;

    public ReadOnlyDictionary()
    {
        _dictionary = new Dictionary<TKey, TValue>();
    }

    public ReadOnlyDictionary(IDictionary<TKey, TValue> dictionary)
    {
        _dictionary = dictionary;
    }

    #region IDictionary<TKey,TValue> Members

    void IDictionary<TKey, TValue>.Add(TKey key, TValue value)
    {
        throw ReadOnlyException();
    }

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

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

    bool IDictionary<TKey, TValue>.Remove(TKey key)
    {
        throw ReadOnlyException();
    }

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

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

    public TValue this[TKey key]
    {
        get
        {
            return _dictionary[key];
        }
    }

    TValue IDictionary<TKey, TValue>.this[TKey key]
    {
        get
        {
            return this[key];
        }
        set
        {
            throw ReadOnlyException();
        }
    }

    #endregion

    #region ICollection<KeyValuePair<TKey,TValue>> Members

    void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item)
    {
        throw ReadOnlyException();
    }

    void ICollection<KeyValuePair<TKey, TValue>>.Clear()
    {
        throw ReadOnlyException();
    }

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

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

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

    public bool IsReadOnly
    {
        get { return true; }
    }

    bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
    {
        throw ReadOnlyException();
    }

    #endregion

    #region IEnumerable<KeyValuePair<TKey,TValue>> Members

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

    #endregion

    #region IEnumerable Members

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

    #endregion

    private static Exception ReadOnlyException()
    {
        return new NotSupportedException("This dictionary is read-only");
    }
}

11
+1 สำหรับการโพสต์โค้ดที่สมบูรณ์และไม่ใช่แค่ลิงค์ แต่ฉันอยากรู้อยากเห็นตัวสร้างที่ว่างใน ReadOnlyDictionary คืออะไร :-)
ซามูเอลเนฟฟ์

20
ระวังตัวสร้างนั้น หากคุณทำสำเนาอ้างอิงของพจนานุกรมที่ส่งผ่านเป็นไปได้ที่โค้ดภายนอกจะสามารถแก้ไขพจนานุกรม "อ่านอย่างเดียว" ของคุณ คอนสตรัคเตอร์ของคุณควรทำสำเนาอาร์กิวเมนต์แบบเต็มและลึก
askheaves

25
@askheaves: การสังเกตที่ดี แต่จริงๆแล้วมันค่อนข้างมีประโยชน์ที่จะใช้การอ้างอิงดั้งเดิมในประเภทอ่านอย่างเดียว - เก็บตัวแปรส่วนตัวของคุณไว้ในต้นฉบับและปรับเปลี่ยนให้เหมาะกับผู้บริโภคภายนอก ตัวอย่างเช่นตรวจสอบวัตถุ ReadOnlyObservableCollection หรือ ReadOnlyCollection ที่สร้างขึ้นใน: Thomas ให้สิ่งที่ทำงานเหมือนกับที่อยู่ในกรอบ. Net ขอบคุณโทมัส! +1
Matt DeKrey

13
@ user420667: วิธีการนำไปใช้มันเป็น "มุมมองแบบอ่านอย่างเดียวของพจนานุกรมที่ไม่ใช่แบบอ่านอย่างเดียว" รหัสอื่นอาจเปลี่ยนแปลงเนื้อหาของพจนานุกรมต้นฉบับและการเปลี่ยนแปลงเหล่านี้จะปรากฏในพจนานุกรมอ่านอย่างเดียว อาจเป็นพฤติกรรมที่ต้องการหรือไม่ขึ้นอยู่กับสิ่งที่คุณต้องการที่จะบรรลุ ...
โทมัส Levesque

6
@Thomas: นั่นเป็นเหมือนกับ ReadOnlyCollection ใน. NET BCL มันเป็นมุมมองแบบอ่านอย่างเดียวในคอลเลกชันที่ไม่แน่นอน อ่านอย่างเดียวไม่ได้หมายความว่าไม่ควรเปลี่ยนรูปและไม่เปลี่ยนรูป
Jeff Yates

229

.NET 4.5

.NET Framework 4.5 BCL แนะนำReadOnlyDictionary<TKey, TValue>( แหล่งที่มา )

เนื่องจาก. NET Framework 4.5 BCL ไม่มีAsReadOnlyพจนานุกรมสำหรับพจนานุกรมคุณจะต้องเขียนของคุณเอง (ถ้าคุณต้องการ) มันจะเป็นอะไรบางอย่างที่ดังต่อไปนี้ความเรียบง่ายซึ่งบางทีไฮไลท์ว่าทำไมมันไม่สำคัญสำหรับ. NET 4.5

public static ReadOnlyDictionary<TKey, TValue> AsReadOnly<TKey, TValue>(
    this IDictionary<TKey, TValue> dictionary)
{
    return new ReadOnlyDictionary<TKey, TValue>(dictionary);
}

.NET 4.0 และต่ำกว่า

ก่อนที่จะ .NET 4.5 ไม่มีกรอบ NET ชั้นที่ล้อมDictionary<TKey, TValue>เช่นReadOnlyCollection wraps รายการ อย่างไรก็ตามมันไม่ยากที่จะสร้าง

นี่คือตัวอย่าง - มีคนอื่น ๆ ถ้าคุณGoogle เพื่อ ReadOnlyDictionary


7
ดูเหมือนว่าพวกเขาจะจำAsReadOnly()วิธีการตามปกติDictionary<,>ไม่ได้ดังนั้นฉันจึงสงสัยว่ามีกี่คนที่จะค้นพบรูปแบบใหม่ของพวกเขา แม้ว่าเธรดสแต็กโอเวอร์โฟลว์นี้จะช่วยได้
Jeppe Stig Nielsen

@Jeppe: ฉันสงสัยว่ามันเกี่ยวกับความทรงจำ คุณลักษณะทุกอย่างมีค่าใช้จ่ายและฉันสงสัยว่า AsReadOnly สูงในรายการลำดับความสำคัญโดยเฉพาะอย่างยิ่งเนื่องจากง่ายต่อการเขียน
Jeff Yates

1
โปรดทราบว่านี่เป็นเพียงเสื้อคลุม; การเปลี่ยนแปลงในพจนานุกรมพื้นฐาน (อันที่ผ่านไปยังตัวสร้าง) จะยังคงกลายพันธุ์พจนานุกรมอ่านอย่างเดียว ดูstackoverflow.com/questions/139592/…
TrueWill

1
@JeffYates พิจารณาว่ามันง่ายแค่ไหนการเขียนมันใช้เวลาน้อยกว่าการตัดสินใจว่าจะใช้เวลาเขียนหรือไม่ ด้วยเหตุนี้การเดิมพันของฉันจึงเป็น "พวกเขาลืม"
Dan Bechard

ตามที่ TrueWill ระบุไว้พจนานุกรมพื้นฐานยังคงสามารถกลายพันธุ์ได้ คุณอาจต้องการพิจารณาส่งผ่านโคลนของพจนานุกรมต้นฉบับไปยังตัวสร้างหากคุณต้องการการเปลี่ยนแปลงไม่ได้จริง (สมมติว่าคีย์และประเภทค่ายังไม่เปลี่ยนรูป)
user420667

19

มีการประกาศในการประชุม BUILDเมื่อเร็ว ๆ นี้ว่าตั้งแต่. NET 4.5 System.Collections.Generic.IReadOnlyDictionary<TKey,TValue>จะมีการรวมส่วนต่อประสาน หลักฐานอยู่ที่นี่ (โมโน) และที่นี่ (Microsoft);)

ไม่แน่ใจว่าReadOnlyDictionaryรวมอยู่ด้วยหรือไม่ แต่อย่างน้อยเมื่อมีอินเทอร์เฟซมันก็ไม่ควรสร้างได้ยากตอนนี้การติดตั้งที่เปิดเผยอินเตอร์เฟซทั่วไป. NET :)


5
ReadOnlyDictionary<TKey, TValue>(.Net 4.5) - msdn.microsoft.com/en-us/library/gg712875.aspx
myermian

18

รู้สึกอิสระที่จะใช้เสื้อคลุมที่เรียบง่ายของฉัน มันไม่ได้ติดตั้ง IDictionary ดังนั้นจึงไม่จำเป็นต้องใส่ข้อยกเว้นตอนรันไทม์สำหรับวิธีการในพจนานุกรมที่จะเปลี่ยนพจนานุกรม วิธีการเปลี่ยนก็ไม่ได้มี ฉันสร้างอินเทอร์เฟซของตัวเองเรียกว่า IReadOnlyDictionary

public interface IReadOnlyDictionary<TKey, TValue> : IEnumerable
{
    bool ContainsKey(TKey key);
    ICollection<TKey> Keys { get; }
    ICollection<TValue> Values { get; }
    int Count { get; }
    bool TryGetValue(TKey key, out TValue value);
    TValue this[TKey key] { get; }
    bool Contains(KeyValuePair<TKey, TValue> item);
    void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex);
    IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator();
}

public class ReadOnlyDictionary<TKey, TValue> : IReadOnlyDictionary<TKey, TValue>
{
    readonly IDictionary<TKey, TValue> _dictionary;
    public ReadOnlyDictionary(IDictionary<TKey, TValue> dictionary)
    {
        _dictionary = dictionary;
    }
    public bool ContainsKey(TKey key) { return _dictionary.ContainsKey(key); }
    public ICollection<TKey> Keys { get { return _dictionary.Keys; } }
    public bool TryGetValue(TKey key, out TValue value) { return _dictionary.TryGetValue(key, out value); }
    public ICollection<TValue> Values { get { return _dictionary.Values; } }
    public TValue this[TKey key] { get { return _dictionary[key]; } }
    public bool Contains(KeyValuePair<TKey, TValue> item) { return _dictionary.Contains(item); }
    public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) { _dictionary.CopyTo(array, arrayIndex); }
    public int Count { get { return _dictionary.Count; } }
    public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() { return _dictionary.GetEnumerator(); }
    IEnumerator IEnumerable.GetEnumerator() { return _dictionary.GetEnumerator(); }
}

4
+1 สำหรับการไม่ละเมิดIDictionaryสัญญา ฉันคิดว่ามันถูกต้องมากขึ้นจากมุมมองของ OOP สำหรับการสืบทอดมาจากIDictionary IReadOnlyDictionary
Sam

@ ตกลงเห็นด้วยและถ้าเราสามารถกลับไปฉันคิดว่ามันจะดีที่สุดและถูกต้องที่สุดที่จะมีIDictionary(สำหรับปัจจุบันIReadOnlyDictionary) และIMutableDictionary(สำหรับปัจจุบันIDictionary)
MasterMastic

1
@MasterMastic นั่นเป็นข้อเสนอที่แปลกประหลาด ฉันไม่จำคลาสอื่น ๆ ในตัวที่ใช้สมมติฐานย้อนกลับว่าคอลเลกชันที่ไม่เปลี่ยนรูปนั้นเป็นสิ่งที่ผู้ใช้คาดหวังตามค่าเริ่มต้น
Dan Bechard

11

IsReadOnly เปิดIDictionary<TKey,TValue>มาจากICollection<T>( IDictionary<TKey,TValue>ขยายICollection<T>เป็นICollection<KeyValuePair<TKey,TValue>>) มันไม่ได้ใช้หรือนำไปใช้ในทางใด ๆ (และในความเป็นจริง "ซ่อน" ผ่านการใช้งานของICollection<T>สมาชิกที่เกี่ยวข้องอย่างชัดเจน)

มีอย่างน้อย 3 วิธีที่ฉันเห็นเพื่อแก้ไขปัญหา:

  1. ใช้การอ่านอย่างเดียวที่กำหนดเองIDictionary<TKey, TValue>และตัด / มอบหมายให้กับพจนานุกรมภายในตามที่ได้รับการแนะนำ
  2. ส่งคืน ICollection<KeyValuePair<TKey, TValue>>ชุดเป็นแบบอ่านอย่างเดียวหรือ IEnumerable<KeyValuePair<TKey, TValue>>ขึ้นอยู่กับการใช้ค่า
  3. โคลนพจนานุกรมโดยใช้ตัวสร้างการคัดลอก.ctor(IDictionary<TKey, TValue>)และส่งคืนสำเนา - วิธีที่ผู้ใช้มีอิสระที่จะทำกับมันตามที่พวกเขาโปรดและมันจะไม่ส่งผลกระทบต่อสถานะของวัตถุที่โฮสต์พจนานุกรมต้นฉบับ โปรดทราบว่าหากพจนานุกรมที่คุณโคลนมีประเภทการอ้างอิง (ไม่ใช่สตริงตามที่แสดงในตัวอย่าง) คุณจะต้องทำการคัดลอก "ด้วยตนเอง" และโคลนประเภทการอ้างอิงเช่นกัน

ในฐานะที่เป็นกัน เมื่อเปิดเผยคอลเลกชันมุ่งหวังที่จะเปิดเผยส่วนต่อประสานที่เล็กที่สุดเท่าที่จะเป็นไปได้ - ในกรณีตัวอย่างควรเป็น IDictionary เนื่องจากจะช่วยให้คุณสามารถปรับใช้การใช้งานพื้นฐานโดยไม่ทำลายสัญญาสาธารณะที่ประเภทนั้นเปิดเผย


8

พจนานุกรมแบบอ่านอย่างเดียวสามารถทดแทนได้ในระดับหนึ่งFunc<TKey, TValue>- โดยปกติฉันจะใช้สิ่งนี้ใน API ถ้าฉันต้องการให้ผู้คนทำการค้นหา มันง่ายและโดยเฉพาะอย่างยิ่งมันง่ายที่จะเปลี่ยนแบ็กเอนด์หากคุณต้องการ อย่างไรก็ตามไม่มีรายการของคีย์ ไม่ว่าเรื่องนั้นขึ้นอยู่กับสิ่งที่คุณทำ


4

ไม่ แต่มันจะง่ายที่จะม้วนของคุณเอง IDictionary จะกำหนดคุณสมบัติ IsReadOnly เพียงห่อพจนานุกรมและโยน NotSupportedException จากวิธีการที่เหมาะสม


3

ไม่มีใน BCL อย่างไรก็ตามฉันเผยแพร่ ReadOnlyDictionary (ชื่อ ImmutableMap) ในโครงการพิเศษ BCLของฉัน

นอกจากจะเป็นพจนานุกรมที่ไม่เปลี่ยนแปลงอย่างสมบูรณ์แล้วยังรองรับการสร้างวัตถุพร็อกซีที่ใช้ IDictionary และสามารถใช้ในสถานที่ที่มีการใช้ IDictionary มันจะส่งข้อยกเว้นเมื่อใดก็ตามที่มีการเรียกใช้ API การกลายพันธุ์

void Example() { 
  var map = ImmutableMap.Create<int,string>();
  map = map.Add(42,"foobar");
  IDictionary<int,string> dictionary = CollectionUtility.ToIDictionary(map);
}

9
ImmutableMap ของคุณจะถูกนำไปใช้เป็นโครงสร้างที่สมดุล เนื่องจากใน. NET ผู้คนคาดหวังว่า "พจนานุกรม" จะถูกนำไปใช้ผ่านการแฮช - และแสดงคุณสมบัติความซับซ้อนที่สอดคล้องกัน - คุณอาจต้องระวังเกี่ยวกับการส่งเสริม ImmutableMap เป็น "พจนานุกรม"
Glenn Slayden

ปรากฏว่าไซต์ code.msdn.com หมดอายุ BCLextras ตอนนี้ที่นี่github.com/scottwis/tiny/tree/master/third-party/BclExtras
BozoJoe

1

คุณสามารถสร้างคลาสที่ใช้งานการดำเนินการบางส่วนของพจนานุกรมเท่านั้นและซ่อนฟังก์ชันเพิ่ม / เอาออก / ตั้งไว้ทั้งหมด

ใช้พจนานุกรมภายในว่าคลาสภายนอกส่งผ่านการร้องขอทั้งหมดไปที่

อย่างไรก็ตามเนื่องจากพจนานุกรมของคุณน่าจะมีประเภทอ้างอิงอยู่จึงไม่มีทางที่คุณจะหยุดผู้ใช้ไม่ให้ตั้งค่าในคลาสที่จัดเก็บโดยพจนานุกรม (ยกเว้นว่าจะอ่านเฉพาะคลาสเหล่านั้นเท่านั้น)


1

ฉันไม่คิดว่าจะมีวิธีที่ง่ายในการทำ ... หากพจนานุกรมของคุณเป็นส่วนหนึ่งของคลาสที่กำหนดเองคุณสามารถทำได้ด้วยตัวทำดัชนี:

public class MyClass
{
  private Dictionary<string, string> _myDictionary;

  public string this[string index]
  {
    get { return _myDictionary[index]; }
  }
}

ฉันต้องสามารถแสดงทั้งพจนานุกรมและตัวทำดัชนีได้
Rob Sobers

ดูเหมือนว่าจะเป็นทางออกที่ดีมาก อย่างไรก็ตามไคลเอนต์ของคลาส MyClass อาจจำเป็นต้องทราบเพิ่มเติมเกี่ยวกับพจนานุกรมเช่นสำหรับการวนซ้ำผ่านมัน และถ้าคีย์ไม่มีอยู่ (อาจเปิดเผย TryGetValue () ในบางรูปแบบเป็นความคิดที่ดี) คุณช่วยทำให้คำตอบและตัวอย่างโค้ดของคุณสมบูรณ์ยิ่งขึ้นได้ไหม?
Peter Mortensen

1

+1 เยี่ยมมากโธมัส ฉันเอา ReadOnlyDictionary หนึ่งก้าวไปอีก

เหมือนการแก้ปัญหาเดลของผมอยากจะลบAdd(), Clear(), Remove()ฯลฯ จาก IntelliSense IDictionary<TKey, TValue>แต่ผมอยากวัตถุที่ได้มาของฉันในการดำเนินการ

นอกจากนี้ฉันต้องการรหัสต่อไปนี้เพื่อทำลาย: (อีกครั้งโซลูชันของ Dale ก็ทำเช่นนี้ด้วย)

ReadOnlyDictionary<int, int> test = new ReadOnlyDictionary<int,int>(new Dictionary<int, int> { { 1, 1} });
test.Add(2, 1);  //CS1061

ผลลัพธ์เพิ่ม () ใน:

error CS1061: 'System.Collections.Generic.ReadOnlyDictionary<int,int>' does not contain a definition for 'Add' and no extension method 'Add' accepting a first argument 

ผู้เรียกยังสามารถส่งไปถึงIDictionary<TKey, TValue>ได้ แต่NotSupportedExceptionจะเพิ่มขึ้นหากคุณพยายามใช้สมาชิกที่ไม่ได้อ่านอย่างเดียว (จากโซลูชันของโทมัส)

อย่างไรก็ตามนี่คือทางออกของฉันสำหรับทุกคนที่ต้องการสิ่งนี้:

namespace System.Collections.Generic
{
    public class ReadOnlyDictionary<TKey, TValue> : IDictionary<TKey, TValue>
    {
        const string READ_ONLY_ERROR_MESSAGE = "This dictionary is read-only";

        protected IDictionary<TKey, TValue> _Dictionary;

        public ReadOnlyDictionary()
        {
            _Dictionary = new Dictionary<TKey, TValue>();
        }

        public ReadOnlyDictionary(IDictionary<TKey, TValue> dictionary)
        {
            _Dictionary = dictionary;
        }

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

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

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

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

        public TValue this[TKey key]
        {
            get { return _Dictionary[key]; }
            set { throw new NotSupportedException(READ_ONLY_ERROR_MESSAGE); }
        }

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

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

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

        public bool IsReadOnly
        {
            get { return true; }
        }

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

        IEnumerator IEnumerable.GetEnumerator()
        {
            return (_Dictionary as IEnumerable).GetEnumerator();
        }

        void IDictionary<TKey, TValue>.Add(TKey key, TValue value)
        {
            throw new NotSupportedException(READ_ONLY_ERROR_MESSAGE);
        }

        bool IDictionary<TKey, TValue>.Remove(TKey key)
        {
            throw new NotSupportedException(READ_ONLY_ERROR_MESSAGE);
        }

        void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item)
        {
            throw new NotSupportedException(READ_ONLY_ERROR_MESSAGE);
        }

        void ICollection<KeyValuePair<TKey, TValue>>.Clear()
        {
            throw new NotSupportedException(READ_ONLY_ERROR_MESSAGE);
        }

        bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
        {
            throw new NotSupportedException(READ_ONLY_ERROR_MESSAGE);
        }
    }
}


0
public IEnumerable<KeyValuePair<string, string>> MyDictionary()
{
    foreach(KeyValuePair<string, string> item in _mydictionary)
        yield return item;
}

2
หรือคุณสามารถทำได้:public IEnumerable<KeyValuePair<string, string>> MyDictionary() { return _mydictionary; }
แพท

0

นี่เป็นทางออกที่ไม่ดีดูที่ด้านล่าง

สำหรับผู้ที่ยังใช้. NET 4.0 ขึ้นไปฉันมีคลาสที่ใช้งานได้เหมือนกับคำตอบที่ยอมรับ แต่สั้นกว่ามาก มันขยายวัตถุพจนานุกรมที่มีอยู่แทนที่สมาชิกบางคน (จริง ๆ แล้วซ่อน) เพื่อให้พวกเขาโยนข้อยกเว้นเมื่อเรียก

หากผู้เรียกพยายามเรียกการเพิ่มลบหรือการดำเนินการกลายพันธุ์อื่น ๆ ที่มีอยู่แล้วภายในพจนานุกรมคอมไพเลอร์จะโยนข้อผิดพลาด ฉันใช้แอตทริบิวต์เลิกใช้เพื่อเพิ่มข้อผิดพลาดคอมไพเลอร์ วิธีนี้คุณสามารถแทนที่พจนานุกรมด้วย ReadOnlyDictionary นี้และดูว่ามีปัญหาใด ๆ เกิดขึ้นทันทีโดยไม่ต้องเรียกใช้แอปพลิเคชันของคุณและรอข้อยกเว้นขณะทำงาน

ลองดูสิ:

public class ReadOnlyException : Exception
{
}

public class ReadOnlyDictionary<TKey, TValue> : Dictionary<TKey, TValue>
{
    public ReadOnlyDictionary(IDictionary<TKey, TValue> dictionary)
        : base(dictionary) { }

    public ReadOnlyDictionary(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> comparer)
        : base(dictionary, comparer) { }

    //The following four constructors don't make sense for a read-only dictionary

    [Obsolete("Not Supported for ReadOnlyDictionaries", true)]
    public ReadOnlyDictionary() { throw new ReadOnlyException(); }

    [Obsolete("Not Supported for ReadOnlyDictionaries", true)]
    public ReadOnlyDictionary(IEqualityComparer<TKey> comparer) { throw new ReadOnlyException(); }

    [Obsolete("Not Supported for ReadOnlyDictionaries", true)]
    public ReadOnlyDictionary(int capacity) { throw new ReadOnlyException(); }

    [Obsolete("Not Supported for ReadOnlyDictionaries", true)]
    public ReadOnlyDictionary(int capacity, IEqualityComparer<TKey> comparer) { throw new ReadOnlyException(); }


    //Use hiding to override the behavior of the following four members
    public new TValue this[TKey key]
    {
        get { return base[key]; }
        //The lack of a set accessor hides the Dictionary.this[] setter
    }

    [Obsolete("Not Supported for ReadOnlyDictionaries", true)]
    public new void Add(TKey key, TValue value) { throw new ReadOnlyException(); }

    [Obsolete("Not Supported for ReadOnlyDictionaries", true)]
    public new void Clear() { throw new ReadOnlyException(); }

    [Obsolete("Not Supported for ReadOnlyDictionaries", true)]
    public new bool Remove(TKey key) { throw new ReadOnlyException(); }
}

วิธีนี้มีปัญหาชี้โดย @supercat แสดงที่นี่:

var dict = new Dictionary<int, string>
{
    { 1, "one" },
    { 2, "two" },
    { 3, "three" },
};

var rodict = new ReadOnlyDictionary<int, string>(dict);
var rwdict = rodict as Dictionary<int, string>;
rwdict.Add(4, "four");

foreach (var item in rodict)
{
    Console.WriteLine("{0}, {1}", item.Key, item.Value);
}

แทนที่จะให้ข้อผิดพลาดเวลาคอมไพล์อย่างที่ฉันคาดไว้หรือข้อยกเว้นรันไทม์อย่างที่ฉันหวังไว้รหัสนี้จะทำงานโดยไม่มีข้อผิดพลาด มันพิมพ์ตัวเลขสี่ตัว นั่นทำให้ ReadOnlyDictionary ของฉันเป็น ReadWriteDictionary


ปัญหาของวิธีการนั้นคือวัตถุดังกล่าวอาจถูกส่งผ่านไปยังวิธีการที่คาดว่าจะมีการDictionary<TKey,TValue>ร้องเรียนโดยไม่ต้องคอมไพเลอร์และการคัดเลือกหรือบังคับใช้การอ้างอิงถึงพจนานุกรมชนิดธรรมดาจะลบการป้องกันใด ๆ
supercat

@supercat อึคุณพูดถูก ฉันคิดว่าฉันมีทางออกที่ดีเช่นกัน
user2023861

ผมจำได้ว่าการทำอนุพันธ์ของDictionaryด้วยวิธีการที่ถูกล่ามโซ่Clone MemberwiseCloneน่าเสียดายที่ในขณะที่มันเป็นไปได้ที่จะทำการโคลนนิ่งพจนานุกรมได้อย่างมีประสิทธิภาพโดยการโคลนร้านค้าสำรองความจริงที่ว่าร้านค้าสำรองนั้นprivateไม่ใช่protectedวิธีที่จะทำให้ชั้นเรียนที่ได้มานั้นทำการโคลน การใช้MemberwiseCloneโดยไม่มีการโคลนร้านค้าสำรองจะหมายความว่าการแก้ไขภายหลังที่ทำกับพจนานุกรมต้นฉบับจะเป็นการทำลายโคลนและการแก้ไขที่ทำกับโคลนจะเป็นการทำลายต้นฉบับ
supercat
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.