我有一个C#类充当字典,所以我现在正在支持IDictionary。
一切都很好,除了属性键和值:
ICollection<TKey> Keys { get; }
ICollection<TValue> Values { get; }
我内部没有键或值的集合,所以我想知道如何将这些作为ICollection提供。
我的第一次尝试是使用“收益率回报”的魔力:
ICollection<TValue> Values {
get {
for( int i = 0; i < nbValues; ++i ) {
yield return GetValue(i);
}
}
}
但是当然这不起作用,因为返回的类型不是IEnumerator而是ICollection ......
这太糟糕了,因为这是最简单的解决方案!
我的第二次尝试是在新创建的数组中复制我的值并返回数组。
ICollection<TValue> Values {
get {
TValue[] copy = new TValue[nbValues];
for( int i = 0; i < nbValues; ++i ) {
copy[i] = GetValue(i);
}
return copy;
}
}
这可以工作,因为Array支持ICollection 但问题是ICollection有添加和删除条目的方法。 如果调用者调用这些方法,则只修改副本而不是字典...
我选择的最终解决方案是让我的字典支持IDictionary,还有ICollection和ICollection,这样我就可以从属性键和值中返回这些集合......
public class MyDictionary : IDictionary<TKey,TValue>,
ICollection<TKey>,
ICollection<TValue>
{
}
现在,属性Keys和Values的get访问器只返回“this”即:字典。
ICollection<TValue> Values {
get {
return this;
}
}
这可能是最理想的解决方案,但我发现每当你想要实现IDictionary时,必须实现两个额外的接口是很麻烦的。
你还有其他想法吗?
我在想,也许将数据作为数组返回并不是一个坏主意。无论如何,IDictionary中已经有了一个添加和删除方法,使用起来更有意义。
也许返回一个包装数组的ReadOnlyCollection会更好,因为任何修改返回集合的尝试都会失败?
ICollection<TValue> Values {
get {
TValue[] copy = new TValue[nbValues];
for( int i = 0; i < nbValues; ++i ) {
copy[i] = GetValue(i);
}
return new System.Collections.ObjectModel.ReadOnlyCollection<TValue>(copy);
}
}
答案 0 :(得分:2)
我个人不希望您能够通过Keys
和Values
从字典中删除键和值 - 我认为不这样做很好
返回ReadOnlyCollection<T>
很好 - 这样调用者只有在尝试修改集合时才会获得异常,而不是被忽略的尝试。
顺便说一下,Dictionary<TKey, TValue>
的行为遵循:
using System;
using System.Collections.Generic;
class Test
{
static void Main()
{
IDictionary<string, string> dictionary =
new Dictionary<string, string> {{ "a", "b" }};
dictionary.Keys.Clear();
Console.WriteLine(dictionary.Count);
}
}
结果:
Unhandled Exception: System.NotSupportedException: Mutating a key collection
derived from a dictionary is not allowed.
at System.Collections.Generic.Dictionary`2
.KeyCollection.System.Collections.Generic.ICollection<TKey>.Clear()
at Test.Main()
正如SLaks所说,如果你可以创建自己的ICollection<T>
实现,这是懒惰的,那会更好 - 但如果由于某种原因这很棘手,或者确实如果性能在你的情况下并不重要,只需创建数组并将其包装在ReadOnlyCollection<T>
中就可以了。你应该考虑记录预期的表现。
如果你做创建自己的延迟实现,有一点需要注意:你应该有一些“版本号”,以确保在基础数据发生变化时使返回的集合无效。
答案 1 :(得分:1)
ReadOnlyCollection
是您列出的选项的最佳方法;这些集合不应该是可写的。
然而,你的吸气剂是O(n),这是不好的。
正确的方法是创建自己的集合类,实现ICollection<T>
并返回字典的实时视图。 (并从突变方法中抛出异常)
这是Dictionary<TKey, TValue>
采取的方法;它确保属性getter快速,并且不会浪费额外的内存。
答案 2 :(得分:0)
谢谢大家的回答。
所以我最终做了两个实用程序类,它们实现了Keys和Values属性所请求的两个ICollection。我有一些字典,我需要添加对IDictionary的支持,所以我将重复使用它们几次:
以下是密钥集合的类:
public class ReadOnlyKeyCollectionFromDictionary< TDictionary, TKey, TValue >
: ICollection<TKey>
where TDictionary : IDictionary<TKey,TValue>, IEnumerable<TKey>
{
IDictionary<TKey, TValue> dictionary;
public ReadOnlyKeyCollectionFromDictionary(TDictionary inDictionary)
{
dictionary = inDictionary;
}
public bool IsReadOnly {
get { return true; }
}
Here I implement ICollection<TKey> by simply calling the corresponding method on
the member "dictionary" but I throw a NotSupportedException for the methods Add,
Remove and Clear
public IEnumerator<TKey> GetEnumerator()
{
return (dictionary as IEnumerable<TKey>).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return (dictionary as IEnumerable).GetEnumerator();
}
}
以下是值集合的类:
public class ReadOnlyValueCollectionFromDictionary<TDictionary, TKey, TValue>
: ICollection<TValue>
where TDictionary : IDictionary<TKey, TValue>, IEnumerable<TValue>
{
IDictionary<TKey, TValue> dictionary;
public ReadOnlyValueCollectionFromDictionary(TDictionary inDictionary)
{
dictionary = inDictionary;
}
public bool IsReadOnly {
get { return true; }
}
Here I implement ICollection<TValue> by simply calling the corresponding method on
the member "dictionary" but I throw a NotSupportedException for the methods Add,
Remove and Clear
// I tried to support this one but I cannot compare a TValue with another TValue
// by using == since the compiler doesn't know if TValue is a struct or a class etc
// So either I add a generic constraint to only support classes (or ?) or I simply
// don't support this method since it's ackward in a dictionary anyway to search by
// value. Users can still do it themselves if they insist.
bool IEnumerable<TValue>.Contains(TValue value)
{
throw new System.NotSupportedException("A dictionary is not well suited to search by values");
}
public IEnumerator<TValue> GetEnumerator()
{
return (dictionary as IEnumerable<TValue>).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return (dictionary as IEnumerable).GetEnumerator();
}
}
然后,如果我的字典支持IEnumerable for TKey和TValue,一切都变得如此简单:
public class MyDictionary : IDictionary<SomeKey,SomeValue>,
IEnumerable<SomeKey>,
IEnumerable<SomeValue>
{
IEnumerator<SomeKey> IEnumerable<SomeKey>.GetEnumerator()
{
for ( int i = 0; i < nbElements; ++i )
{
yield return GetKeyAt(i);
}
}
IEnumerator<SomeValue> IEnumerable<SomeValue>.GetEnumerator()
{
for ( int i = 0; i < nbElements; ++i )
{
yield return GetValueAt(i);
}
}
// IEnumerator IEnumerable.GetEnumerator() is already implemented in the dictionary
public ICollection<SomeKey> Keys
{
get
{
return new ReadOnlyKeyCollectionFromDictionary< MyDictionary, SomeKey, SomeValue>(this);
}
}
public ICollection<Value> Values
{
get
{
return new ReadOnlyValueCollectionFromDictionary< MyDictionary, SomeKey, SomeValue >(this);
}
}
}
它太糟糕了IDictionary没有为属性Keys和Values返回IEnumerable而不是ICollection。所有这一切都会变得如此简单!