我编写了以下扩展方法来从字典中获取元素or null if the key isn't present:
public static TValue ItemOrNull<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key)
{
try
{
return dict[key];
}
catch (KeyNotFoundException ex)
{
return default(TValue);
}
}
我注意到我的程序运行速度非常慢,所以我使用高精度计时器类跟踪了这个扩展方法的问题。我得到了类似的结果〜连续100次:
DebugTimer.ResetTimer();
dict1.ItemOrNull(key);
dict2.ItemOrNull(key);
DebugTimer.StopTimer();
需要大约110,000,000个刻度(在我的处理器上超过0.03秒)。虽然更详细的版本:
DebugTimer.ResetTimer();
if (dict1.ContainsKey(key))
y = dict1[key];
if (dict2.ContainsKey(key))
z = dict2[key];
DebugTimer.StopTimer();
MessageBox.Show(y.ToString(), z.ToString()) // so the compiler doesn't optimize away y and z
需要大约6,000个小时(小于0.000002秒)。
为什么我的扩展方法版本比详细版本花费的时间长4个数量级,这是否清楚?
答案 0 :(得分:15)
不要捕获流量控制的异常 - 它不仅仅会导致性能问题(尽管它们并不像大多数人想象的那么糟糕 - 正如Eric所说,大多数人对异常性能的恐惧来自于使用调试器)。它更多的是关于异常的逻辑性质。就个人而言,我不会以这种方式使用例外,即使它们基本上是免费的。
这里发生了什么不好的事吗?对于用户要求此密钥值的任何方式都无效吗?绝对不是 - 该方法的全部目的是提供默认值。关键是缺席并不是特例 - 所以你应该寻找一种无异常的工作方式。
现在Dictionary<,>
已经有了一种方法,可以让您在密钥存在时获取值,并让您知道是否找到了该值:TryGetValue
。
public static TValue GetValueOrDefault<TKey, TValue>(
this IDictionary<TKey, TValue> dict,
TKey key)
{
TValue ret;
// We don't care about the return value - we want default(TValue)
// if it returns false anyway!
dict.TryGetValue(key, out ret);
return ret;
}
扩展方法只是编译成常规静态方法调用,因此它们没有性能差异。
您可能还想添加一个重载,允许用户在未找到密钥时表达要返回的默认值:
public static TValue GetValueOrDefault<TKey, TValue>(
this IDictionary<TKey, TValue> dict,
TKey key, TValue defaultValue)
{
TValue ret;
return dict.TryGetValue(key, out value) ? ret : defaultValue;
}
顺便提一下,我已调整方法的名称以匹配TryGetValue
。显然你不必遵循它 - 这只是一个建议。
答案 1 :(得分:3)
这是因为您不在详细版本中使用异常。制作一个使用ContainsKey
并进行比较的扩展方法。
澄清:异常可能非常缓慢。这是不依赖它们来控制正常工作流程的原因之一。它们适用于实际意外和不需要的情况。
对于FW3.5 +,请像其他人建议的那样使用TryGetValue
。
答案 2 :(得分:1)
这里有两个问题:
Dictionary.TryGetValue
),时间将会更加接近。答案 3 :(得分:0)
Dictionary是一个哈希表,因此Dictionary.Contains运行得很快(这里调用一步),它的成本很低,因为它直接检查内存中与键相关的值。 [键 - &GT;哈希然后检查内存]
但尝试部分的第一个功能已经检查过了!在返回之前,对值键进行哈希处理并检查内存。不久,它已经包含了Dictionary.Contains的步骤。
你的代码运行的原因是“try”块的速度慢,其中包括具有缺失bool值的Dictionary.Contains的步骤,导致陷入catch块。这浪费了你的时间。上面的答案是正确的,更详细的。
答案 4 :(得分:0)
你只需要将时间值添加到我的教授声明中,“异常处理是一个代价高昂的过程!不要在Catch块中编写所有逻辑,尝试编写代码使得你有可以避免异常的检查。”使用异常处理来处理意外异常,而不是明显当项目不在字典中时会崩溃!