【问题标题】:C# IDictionary.Keys and IDictionary.Values: what is the most optimal implementation?C# IDictionary.Keys 和 IDictionary.Values:最佳实现是什么?
【发布时间】:2013-02-07 16:23:25
【问题描述】:

我有一个用作字典的 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,这样我就可以从属性 Keys 和 Values 中返回这些集合...

public class MyDictionary : IDictionary<TKey,TValue>, 
                            ICollection<TKey>, 
                            ICollection<TValue>
{
}

所以现在属性 Keys 和 Values 的 get 访问器只返回“this”,即:字典。

ICollection<TValue> Values { 
    get {
        return this;
    }
}

这可能是最理想的解决方案,但我发现每当您想要实现 IDictionary 时都必须实现两个额外的接口很麻烦。

你还有什么想法吗?

我在想,也许将副本作为数组返回并不是一个坏主意。无论如何,IDictionary 中已经有一个 Add 和 Remove 方法,使用起来更有意义。

也许返回一个包装数组的 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);
    }
}

【问题讨论】:

  • 你的课与 Dictionary 有何不同?

标签: c# key-value icollection idictionary


【解决方案1】:

我个人不希望您能够通过KeysValues 从字典中删除键和值 - 我认为这样做很好。

返回 ReadOnlyCollection&lt;T&gt; 很好 - 这样调用者在尝试修改集合时只会得到一个异常,而不是只是默默地忽略该尝试。

顺便说一句,该异常遵循Dictionary&lt;TKey, TValue&gt; 的行为:

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&lt;T&gt; 实现,它是惰性的,那会更好 - 但如果由于某种原因这很棘手,或者实际上在您的情况下性能并不重要,只需创建数组并将其包装在ReadOnlyCollection&lt;T&gt; 中就可以了。不过,您应该考虑以任何一种方式记录预期的性能。

如果您确实创建了自己的惰性实现,需要注意的一点是:您可能应该有某种“版本号”,以确保在基础数据发生更改时使返回的集合无效。

【讨论】:

    【解决方案2】:

    ReadOnlyCollection 是您列出的选项中的最佳方法;这些集合不应该是可写的。

    但是你的 getter 是 O(n),这不好。

    正确的方法是创建自己的集合类来实现ICollection&lt;T&gt; 并返回字典的实时视图。 (并从变异方法中抛出异常)

    这是Dictionary&lt;TKey, TValue&gt;采取的做法;它确保了属性获取器的速度很快,并且不会浪费额外的内存。

    【讨论】:

      【解决方案3】:

      谢谢大家的回答。

      所以我最终做了两个实用程序类,它们实现了 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();
          }
      }
      

      那么,如果我的字典支持 TKey 和 TValue 的 IEnumerable,一切就变得如此简单:

      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 没有返回 IEnumerable 而不是 ICollection 属性键和值,这太糟糕了。这一切都会变得容易得多!

      【讨论】:

        猜你喜欢
        • 2012-03-31
        • 1970-01-01
        • 2011-05-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-11-12
        • 2019-08-28
        • 1970-01-01
        相关资源
        最近更新 更多