.net:添加字典项 - 检查它是否存在或允许异常?

时间:2009-09-11 21:13:01

标签: .net dictionary

我正在向StringDictionary添加项目,并且可能会出现重复的密钥。这当然会抛出异常。

如果重复的可能性非常低(即很少发生),我最好使用Try Catch块并使其处理不当,或者我应该在添加每个条目之前总是进行.ContainsKey检查吗?

我假设如果重复密钥的可能性很高,那么允许例外将是一个糟糕的决定,因为它们很昂贵。

思想?

修改

我在泛型词典中使用了反射器,并为ContainsKey和TryGetValue找到了以下内容,因为两者都在下面提到。

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);
}

我错过了什么,或者TryGetValue比ContainsKey做更多的工作?


我很欣赏这些回复,对于我目前的目的,我将继续做一个ContainsKey调用,因为集合很小,而且代码更具可读性。

9 个答案:

答案 0 :(得分:16)

如何处理这取决于发生碰撞时您想要做什么。如果要保留第一个插入值,则应在插入前使用ContainsKey进行检查。另一方面,如果您想使用该键的 last 值,您可以这样做:

// c# sample:
myDictionary[key] = value;

作为旁注:如果可能,我可能会使用Dictionary<string, string>代替StringDictionary。如果没有别的东西可以让你访问更多的Linq扩展方法。

答案 1 :(得分:7)

我会进行包含检查。

我的理由是应该为那些不应该发生的事情保存例外。如果他们这样做,那么应该响起警报铃声并引入调用。对于我来说,对于已知问题案例处理使用例外情况似乎很奇怪,特别是当你可以测试它时。

答案 2 :(得分:5)

如果可能,请将StringDictionary替换为Dictionary<string, string>,然后使用TryGetValue。这避免了异常处理开销和双重查找。

答案 3 :(得分:4)

我做了一些基准测试。但我必须重申Kelsey的观点:

  

应该为那些不应该的东西保存例外   发生。如果他们这样做,那么应该敲响警钟,并带来咒语   对于我来说,对已知问题案例使用例外似乎很奇怪   特别是当你可以测试它时。

这是有道理的,因为通过选择try-catch(如果有的话)获得的性能提升是微不足道的,但“捕获”可能更具惩罚性。这是测试:

public static void Benchmark(Action method, int iterations = 10000)
{
    Stopwatch sw = new Stopwatch();
    sw.Start();
    for (int i = 0; i < iterations; i++)
        method();

    sw.Stop();
    MessageBox.Show(sw.Elapsed.TotalMilliseconds.ToString());
}

public static string GetRandomAlphaNumeric()
{
    return Path.GetRandomFileName().Replace(".", "").Substring(0, 8);
}

var dict = new Dictionary<string, int>();

没有重复:

Benchmark(() =>
{
    // approach 1
    var key = GetRandomAlphaNumeric();
    if (!dict.ContainsKey(key))
        dict.Add(item, 0);

    // approach 2
    try
    {
        dict.Add(GetRandomAlphaNumeric(), 0);
    }
    catch (ArgumentException)
    {

    }
}, 100000);  

50%重复:

for (int i = 0; i < 50000; i++)
{
    dict.Add(GetRandomAlphaNumeric(), 0);  
}

var lst = new List<string>();
for (int i = 0; i < 50000; i++)
{
    lst.Add(GetRandomAlphaNumeric());
}
lst.AddRange(dict.Keys);
Benchmark(() =>
{
    foreach (var key in lst)
    {
        // approach 1
        if (!dict.ContainsKey(key))
            dict.Add(key, 0);

        // approach 2
        try
        {
            dict.Add(key, 0);
        }
        catch (ArgumentException)
        {

        }
    }
}, 1);  

100%重复

var key = GetRandomAlphaNumeric();
dict.Add(key, 0);
Benchmark(() =>
{
    // approach 1
    if (!dict.ContainsKey(key))
        dict.Add(item, 0);

    // approach 2
    try
    {
        dict.Add(key, 0);
    }
    catch (ArgumentException)
    {

    }
}, 100000);

结果

  

没有重复

     

方法1:调试 - &gt; 630毫秒 - 680毫秒;发布 - &gt; 620毫秒 - 640毫秒

     

方法2:调试 - &gt; 640毫秒 - 690毫秒;发布 - &gt; 640毫秒 - 670毫秒

     

50%重复

     

方法1:调试 - &gt; 26毫秒 - 39毫秒;发布 - &gt; 25毫秒 - 33毫秒

     

方法2:调试 - &gt; 1340毫秒;发布 - &gt; 1260毫秒

     

100%重复

     

方法1:调试 - &gt; 7毫秒;发布 - &gt; 7毫秒

     

方法2:调试 - &gt; 2600毫秒;发布 - &gt; 2400毫秒

您可以看到,随着重复项的增加,try-catch表现不佳。即使在最糟糕的情况下,你根本没有重复,try-catch的性能提升也不是很大。

答案 4 :(得分:2)

除非这是一个非常大的字典或代码的关键内循环,否则你可能看不出差异。

.ContainsKey检查每次都会花费你一点性能,而抛出的异常很少会花费你更多的性能。如果重复的可能性确实很低,那就选择允许异常。

如果您确实希望能够管理重复的密钥,您可以查看PowerCollections中的MultiDictionary

答案 5 :(得分:1)

答案 6 :(得分:0)

有两种方法来看待这个......

性能

如果您从性能角度考虑它,则必须考虑:

  • 计算Key和
  • 类型的Hash是多么昂贵
  • 创建和抛出异常的成本是多少

我无法想到任何比抛出异常更昂贵的哈希计算。请记住,当抛出异常时,它们必须跨接到Win32 API进行封送,创建,爬行整个堆栈跟踪,并停止和处理遇到的任何catch块。抛出CLR的异常仍被视为由Win32 API处理的HRESULT。来自Chris Brumme's blog

  

当然,如果不首先考虑Windows结构化异常处理(SEH),我们就无法讨论托管异常。我们还需要查看C ++异常模型。 这是因为托管异常和C ++异常都是在底层SEH机制之上实现的,并且因为托管异常必须与SEH和C ++互操作   异常。

绩效结论:避免例外

最佳实践

.NET Framework设计指南是一个很好的规则集(除了罕见的例外 - 双关语意图)。 Design Guidelines Update: Exception Throwing。有一种叫做#34; Try / Doer&#34;准则中提到的模式,在这种情况下建议避免例外:

  

考虑成员的Tester-Doer模式,这些模式可能会在常见场景中抛出异常,以避免与异常相关的性能问题。

异常也不应该被用作控制流机制 - 再次写在CLR设计指南中。

最佳实践结论:避免例外

答案 7 :(得分:0)

到目前为止,这个问题要简单得多。只需使用TryAdd方法。它可以处理您最优雅的方法。

答案 8 :(得分:-1)

我尽量避免在任何地方使用异常 - 它们处理起来很昂贵,并且它们会使代码复杂化。既然你知道碰撞是可能的,并且执行.Contains检查是微不足道的,我会这样做。