187

我在我的只读属性中返回对字典的引用。如何防止消费者更改我的数据?如果这是一个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

15 回答 15

229

.NET 4.5

.NET Framework 4.5 BCL 引入ReadOnlyDictionary<TKey, TValue>来源)。

由于 .NET Framework 4.5 BCL 不包含AsReadOnlyfor 字典,因此您需要自己编写(如果需要)。它类似于以下内容,其简单性可能突出了为什么它不是 .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一样包装List。但是,创建一个并不难。

这是一个例子——如果你用谷歌搜索 ReadOnlyDictionary,还有很多其他的例子。

于 2009-03-24T17:27:00.037 回答
157

这是一个包装字典的简单实现:

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");
    }
}
于 2009-08-12T23:42:24.767 回答
19

在最近的BUILD 会议上宣布,从 .NET 4.5 开始,该接口System.Collections.Generic.IReadOnlyDictionary<TKey,TValue>被包括在内。证明在这里(单声道)和这里(微软);)

不确定是否ReadOnlyDictionary也包括在内,但至少对于接口,现在创建一个公开官方 .NET 通用接口的实现应该不难:)

于 2011-09-21T23:35:48.887 回答
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(); }
}
于 2011-08-01T21:23:41.490 回答
11

IsReadOnly onIDictionary<TKey,TValue>继承自ICollection<T>( IDictionary<TKey,TValue>extends ICollection<T>as 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,因为这允许您在不破坏类型公开的公共合同的情况下改变底层实现。

于 2009-08-12T23:20:10.583 回答
8

只读字典在某种程度上可以替换为Func<TKey, TValue>- 如果我只希望人们执行查找,我通常在 API 中使用它;这很简单,特别是如果您愿意,更换后端也很简单。但是,它不提供键列表;这是否重要取决于你在做什么。

于 2011-05-24T09:12:05.627 回答
4

不,但很容易自己动手。IDictionary 确实定义了 IsReadOnly 属性。只需包装一个 Dictionary 并从适当的方法中抛出一个 NotSupportedException 。

于 2009-03-24T17:26:48.477 回答
3

BCL 中没有可用的。但是我在我的BCL Extras 项目中发布了一个 ReadOnlyDictionary(名为 ImmutableMap)

除了是一个完全不可变的字典之外,它还支持生成一个实现 IDictionary 的代理对象,并且可以在任何使用 IDictionary 的地方使用。每当调用其中一个变异 API 时,它都会抛出异常

void Example() { 
  var map = ImmutableMap.Create<int,string>();
  map = map.Add(42,"foobar");
  IDictionary<int,string> dictionary = CollectionUtility.ToIDictionary(map);
}
于 2009-03-24T18:27:44.837 回答
1

您可以创建一个仅实现字典的部分实现并隐藏所有添加/删除/设置功能的类。

在内部使用外部类将所有请求传递给的字典。

但是,由于您的字典可能包含引用类型,因此您无法阻止用户对字典保存的类设置值(除非这些类本身是只读的)

于 2009-03-24T17:26:02.910 回答
1

我认为没有一种简单的方法可以做到这一点......如果您的字典是自定义类的一部分,您可以使用索引器来实现它:

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

  public string this[string index]
  {
    get { return _myDictionary[index]; }
  }
}
于 2009-03-24T17:27:52.437 回答
1

+1 干得好,托马斯。我使 ReadOnlyDictionary 更进一步。

就像 Dale 的解决方案一样,我想从 IntelliSense 中删除Add()Clear()Remove()等。但我希望我的派生对象实现IDictionary<TKey, TValue>.

此外,我想打破以下代码:(同样,戴尔的解决方案也这样做)

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

Add() 行导致:

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如果您尝试使用非只读成员(来自 Thomas 的解决方案),则会引发。

无论如何,对于任何也想要这个的人来说,这是我的解决方案:

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);
        }
    }
}
于 2012-04-16T12:19:55.227 回答
1

现在,有Microsoft 不可变集合( System.Collections.Immutable)。通过 NuGet获取它们。

于 2014-01-09T16:41:15.417 回答
0
public IEnumerable<KeyValuePair<string, string>> MyDictionary()
{
    foreach(KeyValuePair<string, string> item in _mydictionary)
        yield return item;
}
于 2009-03-24T17:32:20.693 回答
0

这是一个糟糕的解决方案,见底部。

对于那些仍在使用 .NET 4.0 或更早版本的人,我有一个类与已接受答案中的类一样工作,但它要短得多。它扩展了现有的 Dictionary 对象,覆盖(实际上是隐藏)某些成员,让它们在调用时抛出异常。

如果调用者试图调用内置 Dictionary 具有的 Add、Remove 或其他一些变异操作,编译器将抛出错误。我使用 Obsolete 属性来引发这些编译器错误。这样,您可以使用此 ReadOnlyDictionary 替换 Dictionary 并立即查看任何问题可能出在哪里,而无需运行您的应用程序并等待运行时异常。

看一看:

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。

于 2014-02-12T20:31:21.347 回答
0

对于使用 .NET Core 来解决此问题的任何人,请查看System.Collections.Immutable

特别是对于不可变字典,您可以使用ImmutableDictionary<TKey, TValue>.Builder

public static ImmutableDictionary<string, string> ToReadOnly(this IDictionary<String, string> d)
{
    var builder = new ImmutableDictionary<string, string>.Builder();
    builder.AddRange(d);
    return builder.ToImmutable();
}
于 2020-08-18T19:09:59.847 回答