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

139*_*ser 6 .net c# dictionary

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

myb*_*ame 19

这取决于你使用的方法是什么.如果您打开参考源,您将看到.

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

就像你看到TryGetValue的那样与ContainsKey+一个数组查找相同.

如果您的逻辑只是检查密钥是否存在于该密钥中,Dictionary并且没有与该密钥相关的其他内容(获取密钥的值),则应使用ContainsKey.

如果要获取特定键的值,TryGetValue则快于

if(dic.ContainsKey(keyValue))
{
    dicValue = dic[keyValue]; // here you do more work!
}
Run Code Online (Sandbox Code Playgroud)

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

所以你在FindEntry方法+数组查找中会去2次,如果你使用键ContainsKey和之后使用该值dic[key].你有FindEntry一个额外的时间调用的开销.


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

相比

string GetValue2(int key)
{
    return (Dict.ContainsKey(key))
        ? Dict[key]
        : string.Empty;
}
Run Code Online (Sandbox Code Playgroud)

两者都相对较快,并且在大多数情况下两者之间的性能可以忽略不计(参见下面的单元测试).如果我们想要挑剔,使用TryGetValue需要使用out参数,这比常规参数更有开销.如果未找到该类型,则此变量将设置为默认值,对于字符串,该变量为null.在上面的示例中,不使用此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));
}
Run Code Online (Sandbox Code Playgroud)

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

对于命中,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
Run Code Online (Sandbox Code Playgroud)

对于命中,ContainsKey的TryGetValue减慢约13%

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