关于c#:为什么检查字典是否包含密钥更快,而不是在不包含异常的情况下捕获异常?

Why is it faster to check if dictionary contains the key, rather than catch the exception in case it doesn't?

想象一下代码:

1
2
3
4
5
6
public class obj
{
    // elided
}

public static Dictionary<string, obj> dict = new Dictionary<string, obj>();

方法1

1
2
3
4
5
6
7
8
public static obj FromDict1(string name)
{
    if (dict.ContainsKey(name))
    {
        return dict[name];
    }
    return null;
}

方法2

1
2
3
4
5
6
7
8
9
10
11
public static obj FromDict2(string name)
{
    try
    {
        return dict[name];
    }
    catch (KeyNotFoundException)
    {
        return null;
    }
}

我很好奇这两个函数在性能上是否存在差异,因为第一个函数应该比第二个函数慢,因为它需要检查两次字典是否包含值,而第二个函数只需要访问一次字典,但哇,实际上是相反的:

循环1000 000个值(现有100 000个,不存在90 000个):

first function: 306 milliseconds

second function: 20483 milliseconds

为什么?

编辑:正如您在下面这个问题的注释中所注意到的,如果有0个不存在的键,第二个函数的性能实际上比第一个函数稍好。但是,一旦存在至少一个或多个不存在的密钥,第二个密钥的性能就会迅速下降。


一方面,抛出异常本身就很昂贵,因为堆栈必须解除绑定等。另一方面,通过字典的键访问一个值是便宜的,因为它是一个快速的O(1)操作。

顺便说一句:正确的方法是使用TryGetValue

1
2
3
4
obj item;
if(!dict.TryGetValue(name, out item))
    return null;
return item;

这只访问字典一次而不是两次。如果您真的想返回null,如果密钥不存在,可以进一步简化上述代码:

1
2
3
obj item;
dict.TryGetValue(name, out item);
return item;

这是有效的,因为如果不存在带name的密钥,TryGetValueitem设置为null


字典专门设计用于执行超快速键查找。它们被实现为哈希表,并且条目越多,它们相对于其他方法就越快。只有当您的方法未能完成您设计它要做的事情时,才应该使用异常引擎,因为它是一个大的对象集,为您提供了许多处理错误的功能。我曾经构建了一个完整的库类,其中的所有内容都被try-catch块包围过一次,看到包含600多个异常中的每一个单独行的调试输出时,我感到非常震惊!