3

首先让我解释一下为什么我使用 KeyedCollection。我正在构建一个 DLL,并且我有一个需要添加到集合中的项目列表,并让它们保持我放置它们的顺序,但我还需要通过它们的索引和键来访问它们(键是我已经定义的对象的属性)。如果有其他更简单的集合可以做到这一点,请告诉我。

好的,现在我需要能够在 DLL 内部向此集合添加项目,但我需要它以只读方式对 DLL 的最终用户公开可用,因为我不希望他们删除/更改我添加的项目.

我已经搜索了这个网站,其他网站,一般的谷歌,我还没有找到一种方法来获得某种只读的 KeyedCollection。我最接近的是这个页面(http://www.koders.com/csharp/fid27249B31BFB645825BD9E0AFEA6A2CCDDAF5A382.aspx?s=keyedcollection#L28)但我不能让它工作。

更新:

我看了看那些C5课程。那,连同你的其他评论,帮助我更好地理解如何创建我自己的只读类,它似乎工作。但是,当我尝试将常规的转换为只读的时,我遇到了问题。我得到一个编译时无法转换错误。这是我创建的代码(第一个小类是我最初拥有的):

public class FieldCollection : KeyedCollection<string, Field>
{
    protected override string GetKeyForItem(Field field)
    {
        return field.Name;
    }
}

public class ReadOnlyFieldCollection : KeyedCollection<string, Field>
{
    protected override string GetKeyForItem(Field field)
    { return field.Name; }

    new public void Add(Field field)
    { throw new ReadOnlyCollectionException("This collection is read-only."); }

    new public void Clear()
    { throw new ReadOnlyCollectionException("This collection is read-only."); }

    new public void Insert(int index, Field field)
    { throw new ReadOnlyCollectionException("This collection is read-only."); }

    new public bool Remove(string key)
    { throw new ReadOnlyCollectionException("This collection is read-only."); }

    new public bool Remove(Field field)
    { throw new ReadOnlyCollectionException("This collection is read-only."); }

    new public bool RemoveAt(int index)
    { throw new ReadOnlyCollectionException("This collection is read-only."); }
}

如果我定义了这个变量:

private FieldCollection _fields;

然后这样做:

public ReadOnlyFieldCollection Fields;
Fields = (ReadOnlyFieldCollection)_fields;

它无法编译。它们都继承自同一个类,我认为它们会“兼容”。如何将集合转换(或公开)为我刚刚创建的只读类型?

4

4 回答 4

2

我也不知道任何内置解决方案。这是我在评论中给出的建议的一个例子:

    public class LockedDictionary : Dictionary<string, string>
    {
        public override void Add(string key, string value)
        {
            //do nothing or log it somewhere
            //or simply change it to private
        }

        //and so on to Add* and Remove*

        public override string this[string i]
        {
            get
            {
                return base[i];
            }
            private set
            {
                //...
            }
        }
    }

您将能够遍历 KeyValuePair 列表和其他所有内容,但不能用于编写操作。请务必根据您的需要键入它。


编辑 为了有一个排序列表,我们可以将基类从更改Dictionary<T,T>为 a Hashtable

它看起来像这样:

    public class LockedKeyCollection : System.Collections.Hashtable
    {
        public override void Add(object key, object value)
        {
            //do nothing or throw exception?
        }

        //and so on to Add* and Remove*

        public override object this[object i]
        {
            get
            {
                return base[i];
            }
            set
            {
                //do nothing or throw exception?
            }
        }
    }

用法:

        LockedKeyCollection aLockedList = new LockedKeyCollection();

        foreach (System.Collections.DictionaryEntry entry in aLockedList)
        {
            //entry.Key
            //entry.Value
        }

不幸的是,我们无法更改方法的访问修饰符,但我们可以覆盖它们以不做任何事情。

于 2012-08-10T20:20:03.680 回答
2

编辑以修改我的原始答案。显然,我没有足够的咖啡因这个上午。

您应该查看 C5 集合库,它为集合和字典提供只读包装器,以及 CLR 团队忘记的其他内容:

http://www.itu.dk/research/c5/

您还可以通过 NuGet 在http://www.nuget.org/packages/C5获取 C5 Collections 库

于 2012-08-13T17:09:06.377 回答
1

有两个额外的选项可供您使用,将来可能更适合类似的需求。第一个选项最简单,需要的代码最少;第二个需要更多的脚手架,但最适合与现有实现共享只读成员KeyedCollection<TKey, TValue>

选项 1:源自ReadOnlyCollection<TValue>

在这种方法中,您从索引器派生ReadOnlyCollection然后添加this[TKey key]索引器。这类似于@AndreCalil 上面采用的方法,除了ReadOnlyCollection 已经为每个可写入口点抛出一个NotSupportedException,所以你在那里被覆盖。

这是微软内部ReadOnlyKeyedCollection<TKey, TValue>System.ServiceModel.Internals程序集中使用的方法(源代码):

class ReadOnlyKeyedCollection<TKey, TValue> : ReadOnlyCollection<TValue> {

  KeyedCollection<TKey, TValue> innerCollection;

  public ReadOnlyKeyedCollection(KeyedCollection<TKey, TValue> innerCollection) : base(innerCollection) {
    Fx.Assert(innerCollection != null, "innerCollection should not be null");
    this.innerCollection = innerCollection;
  }

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

}

选项 2:装饰现有的KeyedCollection<TKey, TValue>

ReadOnlyCollection<TValue>方法唯一真正的缺点是它不会从您的KeyedCollection<TKey, TValue>实现中继承任何逻辑。如果您有很多自定义逻辑,那么您可能会选择在现有实现上使用装饰器模式。这看起来像:

class MyReadOnlyKeyedCollection : MyKeyedCollection, IReadOnlyCollection<TValue> {

  MyKeyedCollection innerCollection;

  public MyReadOnlyKeyedCollection(MyKeyedCollection innerCollection) : base() {
    this.innerCollection = innerCollection;
  }

  protected override InsertItem(Int32 index, TItem item) {
    throw new NotSupportedException("This is a read only collection");
  }
  //Repeat for ClearItems(), RemoveItem(), and SetItem()

  public override void YourWriteMethod() {
    throw new NotSupportedException("This is a read only collection");
  }
  //Repeat for any other custom writable members

  protected override IList<T> Items { 
    get {
      return.innerCollection.ToList();
    }
  }
  //This should cover all other entry points for read operations

  public override Object YourReadMethod() {
    this.innerCollection.YourReadMethod();
  }
  //Repeat for any other custom read-only members

}

KeyedCollection<TKey, TItem>然而,这显然需要编写更多代码,而且只有在现有界面上有大量自定义代码时才有意义;否则,第一种方法更有意义。鉴于此,最好通过扩展方法或在 C# 8.0 中的默认接口方法集中该逻辑。

于 2017-10-05T00:07:09.157 回答
0

答案永远不会太晚!

最简单的路径:

  • IReadOnlyCollection<T>
  • 使其工作的最小设置,即没有比较器和内部字典

代码:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;

namespace Z
{
    public abstract class ReadOnlyKeyedCollection<TKey, TValue> : IReadOnlyCollection<TValue>
    {
        private readonly IReadOnlyCollection<TValue> _collection;

        protected ReadOnlyKeyedCollection([NotNull] IReadOnlyCollection<TValue> collection)
        {
            _collection = collection ?? throw new ArgumentNullException(nameof(collection));
        }

        public TValue this[[NotNull] TKey key]
        {
            get
            {
                if (key == null)
                    throw new ArgumentNullException(nameof(key));

                foreach (var item in _collection)
                {
                    var itemKey = GetKeyForItem(item);

                    if (Equals(key, itemKey))
                        return item;
                }

                throw new KeyNotFoundException();
            }
        }

        #region IReadOnlyCollection<TValue> Members

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

        public IEnumerator<TValue> GetEnumerator()
        {
            return _collection.GetEnumerator();
        }

        public int Count => _collection.Count;

        #endregion

        public bool Contains([NotNull] TKey key)
        {
            if (key == null)
                throw new ArgumentNullException(nameof(key));

            return _collection.Select(GetKeyForItem).Contains(key);
        }

        protected abstract TKey GetKeyForItem(TValue item);
    }
}
于 2019-03-13T00:40:38.257 回答