为什么我的代码这么慢?

Bri*_*don 1 c# extension-methods profiling

我编写了以下扩展方法来从字典中获取元素,如果键不存在则写入null:

public static TValue ItemOrNull<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key)
{
    try
    {
        return dict[key];
    }
    catch (KeyNotFoundException ex)
    {
        return default(TValue);
    }
}
Run Code Online (Sandbox Code Playgroud)

我注意到我的程序运行得非常慢,所以我使用高精度计时器类跟踪了这个扩展方法的问题.我得到了类似的结果〜连续100次:

DebugTimer.ResetTimer();
    dict1.ItemOrNull(key);
    dict2.ItemOrNull(key);
DebugTimer.StopTimer();
Run Code Online (Sandbox Code Playgroud)

需要大约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
Run Code Online (Sandbox Code Playgroud)

需要大约6,000个滴答(小于0.000002秒).

为什么我的扩展方法版本比冗长的版本花费的时间长4个数量级,这是否清楚?

Jon*_*eet 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;
}
Run Code Online (Sandbox Code Playgroud)

扩展方法只是编译成常规静态方法调用,因此它们没有性能差异.

您可能还想添加一个重载,允许用户在未找到密钥时表示要返回的默认值:

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;
}
Run Code Online (Sandbox Code Playgroud)

TryGetValue顺便说一句,我已经调整了你的方法的名称来匹配.显然你不必遵循它 - 这只是一个建议.