代码之家  ›  专栏  ›  技术社区  ›  ScottE

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

  •  15
  • ScottE  · 技术社区  · 15 年前

    我正在向一个字符串字典添加项,可能会出现一个重复的键。这当然会引发一个异常。

    如果重复的可能性很低(即很少发生),我最好使用一个try-catch块而不处理它,还是应该在添加每个条目之前始终执行.containsKey检查?

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

    思想?

    编辑

    我在通用字典上使用了Reflector,并找到了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调用,因为集合很小,代码更可读。

    8 回复  |  直到 7 年前
        1
  •  15
  •   Fredrik Mörk    15 年前

    如何接近它取决于发生碰撞时您想做什么。如果要保留第一个插入的值,应使用 ContainsKey 插入前检查。另一方面,如果您想使用 最后的 该键的值,可以这样做:

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

    旁注:如果可能的话,我可能会使用 Dictionary<string, string> 而不是 StringDictionary . 如果没有其他东西可以让您访问更多的LINQ扩展方法。

        2
  •  7
  •   Kelsey    15 年前

    我要开包含支票。

    我的理由是应该为那些不应该发生的事情保留例外。如果他们这样做了,警铃应该响了,小牛场也应该进来。对于我来说,将异常用于已知问题案例处理似乎很奇怪,尤其是当您可以测试它时。

        3
  •  5
  •   Pavel Minaev    15 年前

    如果可能,更换 StringDictionary 具有 Dictionary<string, string> 及使用 TryGetValue . 这避免了异常处理开销和双重查找。

        4
  •  4
  •   nawfal Donny V.    11 年前

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

    应该为那些不应该发生的事情保存例外 发生。如果是这样的话,警铃就会响,加略山就会被带来。 在中。对于我来说,将异常用于已知问题案例似乎很奇怪 尤其是当你可以测试的时候。

    这是有意义的,因为你通过追求获得的绩效 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:调试-630 ms-680 ms;版本-620 ms-640 ms

    方法2:调试-640 ms-690 ms;版本-640 ms-670 ms

    50%重复

    方法1:调试-26 ms-39 ms;发布-25 ms-33 ms

    方法2:调试->1340 ms;发布->1260 ms

    100%重复

    方法1:调试->7 ms;发布->7 ms

    方法2:debug->2600 ms;release->2400 ms

    你可以看到,随着重复次数的增加, 尝试捕捉 表现不佳。即使在最坏的情况下,您根本没有复制品,性能提高 尝试捕捉 没有什么实质性的东西。

        5
  •  2
  •   Eric J.    15 年前

    除非这是一个非常大的字典,或者在一个关键的内部代码循环中,否则您可能看不到区别。

    .containsKey检查每次都会花费您一点性能,而抛出的异常很少会花费您一点性能。如果复制的可能性确实很低,则允许例外。

    如果您确实希望能够管理重复的密钥,那么您可以在 PowerCollections

        6
  •  1
  •   cemdev    13 年前
        7
  •  0
  •   Dave Black    7 年前

    有两种方法来看待这个…

    性能

    如果从性能角度来看,则必须考虑:

    • 计算密钥类型的散列值和
    • 创建和抛出异常有多昂贵

    我想不出比抛出异常更昂贵的哈希计算。记住,当抛出异常时,它们已经跨Interop封送到win32 api,被创建,对整个堆栈跟踪进行爬网,并停止并处理遇到的任何catch块。在clr中引发的异常仍然被视为由win32 api在封面下处理的结果。从 Chris Brumme's blog 以下内容:

    当然,我们可以在不首先考虑Windows结构化异常处理(SEH)的情况下讨论托管异常。我们还需要看看C++异常模型。 这是因为两个托管异常和C++异常都是在底层SEH机制之上实现的,并且因为托管异常必须与SEH和C++进行互操作。 例外情况。

    履行义务:避免例外


    最佳实践

    .NET框架设计指导方针是一个很好的规则集,可以遵循(很少有例外情况-有意使用双关语)。 Design Guidelines Update: Exception Throwing . 指南中提到了一种称为“尝试/实干者”的模式,在这种情况下,建议避免例外:

    考虑成员的测试人员-执行者模式,这些成员可能在常见场景中抛出异常,以避免与异常相关的性能问题。

    异常也不应该被用作控制流机制——这也是在clr设计指南中写的。

    最佳实践结论:避免例外

        8
  •  -1
  •   SqlRyan    15 年前

    我尽量避免在任何地方出现异常——它们处理起来都很昂贵,而且会使代码复杂化。既然你知道有可能发生碰撞,那么做这个很简单。包含检查,我会这样做的。