【问题标题】:Why are .NET dictionaries whiny?为什么 .NET 字典会发牢骚?
【发布时间】:2013-12-17 19:00:28
【问题描述】:

在 .NET 中,如果您向字典询问与它没有的键关联的值,则会引发异常。你可以通过调用TryGetValue 来解决这个问题,但我发现它使用 Ref var 可恶。

这意味着您通常必须通过首先检查密钥是否存在 (ContainsKey) 来防止丢失密钥,这看起来很麻烦。

在 Ruby 中,如果您询问与键关联的值的哈希值,您不会得到异常。它只会返回一些默认值,您可以根据需要进行更改。这使得使用哈希更加愉快。

.NET 字典为什么会因为缺少键而大惊小怪,从而使它们更有用?

【问题讨论】:

  • 回答:因为 VB 开发人员希望这样 :P。 Python 的 dict 也抛出异常,C#。在开发库时,您可以决定如何做事。他们想那样做:)

标签: .net vb.net dictionary


【解决方案1】:

问题在于,默认值根本不是关键不在字典中的明确答案。例如,在 .Net 中,类的默认值为 null。对于任何 ref 类型来说,这是一个完全可接受的值,因此可以用作键。

Dictionary<int, string> map = ...;
map.Add(42, null);  // OK

让我们假设 Dictionary&lt;TKey, TValue&gt; 在缺少键时确实返回了默认值。在那个世界里,下面的代码会失败

void FillValue(Dictionary<int, string> map, int value) { 
  if (map[value] == null) {  
    map.Add(value, "");
  }
}

如果字典中已经存在value 且值为null,则此代码将失败。它会抛出,因为Add 要求该值不存在才能正确执行。

最重要的是,使用默认值表示缺失的情况会在代码中产生不可避免的歧义。 Dictionary&lt;TKey, TValue&gt; 的当前设计尽可能避免歧义

还要注意Hashtable 的设计与您建议的一样(缺失值返回null)。 .Net 放弃了这种设计,部分原因是它产生了歧义问题。

【讨论】:

  • 您可以随时询问ContainsKey 是否相关。但是出于所有意图和目的,为什么从 42 返回的 null 足以做出这种区分?在大多数情况下,我编写代码的方式与结果无关。
  • @Mario 当然,在那种情况下你总是可以使用ContainsKey,但是你需要支付两次查找费用(一次用于ContainsKey,另一次用于[])。 TryGetValue 执行这两种操作,并且只支付一次查找成本。字典中的查找力求为 O(1),但即使 O(1) 也不意味着“即时”。这通常是一项非常复杂的操作
  • +1。关于您的最后评论,我从来没有遇到过与在字典上进行两次查找而不是一次查找相关的性能瓶颈。通常是别的东西。 :)
  • @Neolisk 我实际上发现了太多与双重查找相关的性能问题。它们通常不是瓶颈,但有几次我通过删除双重查找将场景减少了 5-15%。
  • @Dan-o 实际上他真的无法通过继承来解决这个问题。 Dictionary&lt;TKey, TValue&gt; 上的所有成员都是非虚拟的,因此无需覆盖。扩展方法会是更好的选择
【解决方案2】:

注意:对于那些来自 Ruby 世界或 C# 世界的人,Ruby 的地图可以与 C# 的字典相媲美。

我会说这是需要的,尤其是在惯用的 C# 中。在 Ruby 中,您可以简单地通过初始化来声明变量,就像您可以从哈希中为未初始化的键检索默认值一样(或者以同样的方式初始化值)。

鲁比:

some_variable = 40
retrieved_value = some_hash['key'] // returns default string

C#:

int some_variable = 40;
Dictionary<string,string> someDictionary = new Dictionary<string,string>();
string retrievedValue = someDictionary["key"]; // THROWS EXCEPTION
someDictionary.Add("key", "value");

似乎在像 C# 这样的静态语言中,想法是在尝试之前预先知道某些东西会按照您的预期工作。 C# 没有回退(如 Ruby 默认值),而是希望您在尝试访问之前知道那里有一些东西。

您可能会认为 Ruby 哈希检索具有两种行为。如果那里有一个值,它会检索它。如果不是,则将其初始化为默认值。我认为这就是 C# 不以这种方式处理它的原因。最重要的是,除非观察到上下文,否则语句的目的(无论是获取现有变量还是有意创建变量)通常是模棱两可的,即使有上下文也很难。


然而,有趣的是,C# 如何处理 assignment 到字典。到目前为止显示的所有内容在命令式 C# 和富有表现力的 Ruby 之间都有明显的区别。但是,在 C# 中,您可以执行以下操作:

Dictionary<int,int> myDictionary = new Dictionary<int,int>;
myDictionary[3] = 4;

这只是以下的简写仅当字典中不存在键 3

myDictionary.Add(3,4);

在这里,我们看到了 Ruby 哈希检索中存在的一些潜在歧义; myDictionary[3] = 4; 如果字典没有 3 条目(将新条目初始化为 4),将做一件事,如果它确实有 3 条目(将其当前值替换为 4),则做另一件事。

或者,myDictionary.Add(3,4); 如果已经有 3 的条目,则会引发异常。对我来说,这感觉更像是一种静态命令式语言,我的意思是“我知道myDictionary 没有 3 作为键,现在我正在添加它”,而其他语法以及 Ruby 的承诺不那么重要。

编辑:感谢 Neolisk,增加了 dictionary.Add()dicitonary[3]=4 之间的区别

【讨论】:

  • +1,附注。 myDictionary[3] = 4; 也允许重新分配。如果条目已经存在,myDictionary.Add(3,4); 会抛出异常。所以这些陈述并不完全等价。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-02-14
  • 2011-06-06
  • 2013-01-13
  • 1970-01-01
  • 1970-01-01
  • 2018-06-13
相关资源
最近更新 更多