是否有人应该使用ContainsKey而不是TryGetValue

时间:2016-10-05 23:24:58

标签: c# .net dictionary

我已经尝试了一些测试,如果有所有的点击或错过也没关系。 TryGetValue总是更快。什么时候应该使用ContainsKey?

2 个答案:

答案 0 :(得分:17)

这取决于你使用的方法是什么。如果您打开Reference source,您会看到。

public bool TryGetValue(TKey key, out TValue value)
{
  int index = this.FindEntry(key);
  if (index >= 0)
  {
    value = this.entries[index].value;
    return true;
  }
  value = default(TValue);
  return false;
}

public bool ContainsKey(TKey key)
{
  return (this.FindEntry(key) >= 0);
}

就像您看到TryGetValueContainsKey +一个数组查找相同。

如果您的逻辑只是检查密钥是否存在于Dictionary中而且与此密钥无关的任何其他内容(获取密钥的值),则应使用ContainsKey。< /强>

如果您想获取特定密钥的值,TryGetValue

更快
if(dic.ContainsKey(keyValue))
{
    dicValue = dic[keyValue]; // here you do more work!
}

关于Dictionary[key]

的逻辑
    public TValue this[TKey key] 
    {
        get {
            int i = FindEntry(key);
            if (i >= 0) return entries[i].value;
            ThrowHelper.ThrowKeyNotFoundException();
            return default(TValue);
        }
        set {
            Insert(key, value, false);
        }
    }

因此,如果您将密钥与FindEntry一起使用,那么您将在ContainsKey方法+数组查找中进行2次,然后使用dic[key]获取值。你有一次额外调用FindEntry的费用。

答案 1 :(得分:7)

从概念上讲,这两种方法非常不同。 ContainsKey只是检查给定的键是否在字典中。 TryGetValue将尝试返回给定键的值,前提是它在字典中。两者都可以很快,取决于你想做什么。

考虑以下方法,该方法从字典返回值或返回string.Empty。

Dictionary<int,string> Dict = new Dictionary<int,string>();
string GetValue1(int key)
{
    string outValue;
    if (!Dict.TryGetValue(key, out outValue))
        outValue = string.Empty;
    return outValue;
}

相比
string GetValue2(int key)
{
    return (Dict.ContainsKey(key))
        ? Dict[key]
        : string.Empty;
}

两者都相对较快,在大多数情况下两者之间的性能可以忽略不计(参见下面的单元测试)。如果我们想要挑剔,使用TryGetValue需要使用out参数,这比常规参数更有开销。如果未找到该类型,则此变量将设置为默认值,对于字符串,该变量为null。在上面的示例中,未使用此空值,但无论如何我们都会产生开销。最后,GetValue1需要使用局部变量outValue,而GetValue2则不需要。

BACON指出GetValue2会在找到值的情况下使用2次查找,这是相对费用的。这是正确的,并且还意味着在未找到密钥的情况下,GetValue2将执行得更快。因此,如果程序期望大多数查找都未命中,请使用GetValue2。否则使用GetValue1。

如果处理ConcurrentDictionary,请使用TryGetValue,因为对它的操作应该是原子的,即一旦在多线程环境中检查值,当您尝试访问该值时,该检查可能不正确。

下面包括2个单元测试,使用具有不同Key类型的字典(int vs string)测试两个方法之间的性能。该基准仅显示两种方法之间的差距如何随着其背景的变化而关闭/扩大。

[TestMethod]
public void TestString()
{
    int counter = 10000000;
    for (var x = 0; x < counter; x++)
        DictString.Add(x.ToString(), "hello");

    TimedLog("10,000,000 hits TryGet", () =>
    {
        for (var x = 0; x < counter; x++)
            Assert.IsFalse(string.IsNullOrEmpty(GetValue1String(x.ToString())));
    }, Console.WriteLine);

    TimedLog("10,000,000 hits ContainsKey", () =>
    {
        for (var x = 0; x < counter; x++)
            Assert.IsFalse(string.IsNullOrEmpty(GetValue2String(x.ToString())));
    }, Console.WriteLine);

    TimedLog("10,000,000 misses TryGet", () =>
    {
        for (var x = counter; x < counter*2; x++)
            Assert.IsTrue(string.IsNullOrEmpty(GetValue1String(x.ToString())));
    }, Console.WriteLine);

    TimedLog("10,000,000 misses ContainsKey", () =>
    {
        for (var x = counter; x < counter*2; x++)
            Assert.IsTrue(string.IsNullOrEmpty(GetValue2String(x.ToString())));
    }, Console.WriteLine);
}

[TestMethod]
public void TestInt()
{
    int counter = 10000000;
    for (var x = 0; x < counter; x++)
        DictInt.Add(x, "hello");

    TimedLog("10,000,000 hits TryGet", () =>
    {
        for (var x = 0; x < counter; x++)
            Assert.IsFalse(string.IsNullOrEmpty(GetValue1Int(x)));
    }, Console.WriteLine);

    TimedLog("10,000,000 hits ContainsKey", () =>
    {
        for (var x = 0; x < counter; x++)
            Assert.IsFalse(string.IsNullOrEmpty(GetValue2Int(x)));
    }, Console.WriteLine);

    TimedLog("10,000,000 misses TryGet", () =>
    {
        for (var x = counter; x < counter * 2; x++)
            Assert.IsTrue(string.IsNullOrEmpty(GetValue1Int(x)));
    }, Console.WriteLine);

    TimedLog("10,000,000 misses ContainsKey", () =>
    {
        for (var x = counter; x < counter * 2; x++)
            Assert.IsTrue(string.IsNullOrEmpty(GetValue2Int(x)));
    }, Console.WriteLine);
}

public static void TimedLog(string message, Action toPerform, Action<string> logger)
{
    var start = DateTime.Now;
    if (logger != null)
        logger.Invoke(string.Format("{0} Started at {1:G}", message, start));

    toPerform.Invoke();
    var end = DateTime.Now;
    var span = end - start;
    if (logger != null)
        logger.Invoke(string.Format("{0} Ended at {1} lasting {2:G}", message, end, span));
}

TestInt的结果

10,000,000 hits TryGet Started at ...
10,000,000 hits TryGet Ended at ... lasting 0:00:00:00.3734136
10,000,000 hits ContainsKey Started at ...
10,000,000 hits ContainsKey Ended at ... lasting 0:00:00:00.4657632
10,000,000 misses TryGet Started at ...
10,000,000 misses TryGet Ended at ... lasting 0:00:00:00.2921058
10,000,000 misses ContainsKey Started at ...
10,000,000 misses ContainsKey Ended at ... lasting 0:00:00:00.2579766

对于命中,ContainsKey的TryGetValue

慢约25%

对于未命中,TryGetValue比ContainsKey慢约13%

TestString的结果

10,000,000 hits TryGet Started at ...
10,000,000 hits TryGet Ended at ... lasting 0:00:00:03.2232018
10,000,000 hits ContainsKey Started at ...
10,000,000 hits ContainsKey Ended at ... lasting 0:00:00:03.6417864
10,000,000 misses TryGet Started at ...
10,000,000 misses TryGet Ended at ... lasting 0:00:00:03.6508206
10,000,000 misses ContainsKey Started at ...
10,000,000 misses ContainsKey Ended at ... lasting 0:00:00:03.4912164

对于点击,ContainsKey的TryGetValue

慢约13%

对于未命中,TryGetValue比ContainsKey慢约4.6%