Programing

.NET에 사용 가능한 읽기 전용 일반 사전이 있습니까?

lottogame 2020. 5. 16. 10:00
반응형

.NET에 사용 가능한 읽기 전용 일반 사전이 있습니까?


읽기 전용 속성에서 사전에 대한 참조를 반환합니다. 소비자가 내 데이터를 변경하지 못하게하려면 어떻게합니까? 이것이 있다면 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

다음은 사전을 감싸는 간단한 구현입니다.

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

.NET 4.5

.NET Framework 4.5 BCL에 ReadOnlyDictionary<TKey, TValue>( source )가 도입되었습니다 .

.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 이전 Dictionary<TKey, TValue>에는 ReadOnlyCollection을 감싸는 것처럼 List 를 감싸는 .NET Framework 클래스가 없습니다 . 그러나 하나를 만드는 것은 어렵지 않습니다.

다음은 예 입니다. Google이 ReadOnlyDictionary를 사용하는 경우 많은 다른 것들이 있습니다 .


최근 BUILD 컨퍼런스 에서 .NET 4.5 이후 인터페이스 System.Collections.Generic.IReadOnlyDictionary<TKey,TValue>가 포함 되었다고 발표 되었습니다. 증명은 여기 (Mono)와 여기 (Microsoft)입니다.)

ReadOnlyDictionary포함되어 있는지 확실하지 않지만 적어도 인터페이스를 사용하면 공식 .NET 일반 인터페이스를 제공하는 구현을 만드는 것이 어렵지 않아야합니다. :)


간단한 래퍼를 사용하십시오. IDictionary를 구현하지 않으므로 런타임에 사전을 변경하는 사전 메서드에 대해 예외를 throw하지 않아도됩니다. 변경 방법은 단순히 존재하지 않습니다. 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(); }
}

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 여야합니다.


읽기 전용 사전은 어느 정도 대체 될 수 있습니다 Func<TKey, TValue>.-검색을 수행하는 사람들 만 원할 경우 일반적으로 API에서이를 사용합니다. 간단하고, 특히 원하는 경우 백엔드를 교체하는 것이 간단합니다. 그러나 키 목록은 제공하지 않습니다. 중요한 것은 당신이하는 일에 달려 있습니다.


아니요,하지만 쉽게 롤업 할 수 있습니다. IDictionary는 IsReadOnly 속성을 정의합니다. 사전을 래핑하고 적절한 메소드에서 NotSupportedException을 던지기 만하면됩니다.


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

사전의 부분 구현 만 구현하고 모든 추가 / 제거 / 설정 기능을 숨기는 클래스를 작성할 수 있습니다.

외부 클래스가 모든 요청을 전달하는 사전을 내부적으로 사용하십시오.

그러나 사전에 참조 유형이있을 수 있으므로 사용자가 사전이 보유한 클래스의 값을 설정하지 못하게 할 방법이 없습니다 (클래스 자체가 읽기 전용이 아닌 경우).


나는 그것을하는 쉬운 방법이 없다고 생각합니다 ... 사전이 사용자 정의 클래스의 일부라면 인덱서를 사용하여 달성 할 수 있습니다.

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

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

잘 했어, 토마스 ReadOnlyDictionary를 한 단계 더 발전 시켰습니다.

많은 데일의 솔루션처럼, 나는 제거하고 싶어 Add(), Clear(), Remove()인텔리에서 등. 그러나 파생 된 객체가 구현되기를 원했습니다 IDictionary<TKey, TValue>.

또한 다음 코드를 깨고 싶습니다. (다시, Dale의 솔루션 도이 작업을 수행합니다)

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

이제 Microsoft Immutable Collections ( System.Collections.Immutable)가 있습니다. 를 가져 NuGet을 통해 .


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

이것은 나쁜 해결책입니다. 하단을 참조하십시오.

For those still using .NET 4.0 or earlier, I have a class that works just like the one in the accepted answer, but it's much shorter. It extends the existing Dictionary object, overriding (actually hiding) certain members to have them throw an exception when called.

If the caller tries to call Add, Remove, or some other mutating operation that the built-in Dictionary has, the compiler will throw an error. I use the Obsolete attributes to raise these compiler errors. This way, you can replace a Dictionary with this ReadOnlyDictionary and immediately see where any problems might be without having to run your application and waiting for run-time exceptions.

Take a look:

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

This solution has a problem pointed out by @supercat illustrated here:

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

Rather than give a compile-time error like I expected, or a runtime-exception like I hoped, this code runs without error. It prints four numbers. That makes my ReadOnlyDictionary a ReadWriteDictionary.

참고URL : https://stackoverflow.com/questions/678379/is-there-a-read-only-generic-dictionary-available-in-net

반응형