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

.net:添加字典项-检查它是否存在或允许异常?,.net,dictionary,.net,Dictionary,我正在向StringDictionary添加项,可能会出现重复键。这当然会引发异常 如果重复的可能性很低(即很少发生),我是否最好使用Try-Catch块并保持其未处理状态,还是应该在添加每个条目之前始终进行.ContainsKey检查 我假设如果重复密钥的可能性很高,那么允许异常将是一个糟糕的决定,因为它们很昂贵 想法 编辑 我在通用字典中使用了reflector,并为ContainsKey和TryGetValue找到了以下内容,如下所述 public bool TryGetValue(TKe

我正在向StringDictionary添加项,可能会出现重复键。这当然会引发异常

如果重复的可能性很低(即很少发生),我是否最好使用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;
}

是我遗漏了什么,还是TryGetValue比Containeskey做了更多的工作



我很感谢您的回复,就我目前的目的而言,我将继续执行ContainsKey调用,因为集合将很小,代码更具可读性。

如何实现这一点取决于发生冲突时您想做什么。如果要保留第一个插入的值,应在插入前使用
ContainsKey
进行检查。另一方面,如果要使用该键的最后一个值,可以这样做:

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

附带说明:如果可能的话,我可能会使用
Dictionary
而不是
StringDictionary
。如果没有其他方法可以让您访问更多的Linq扩展方法。

我会进行包含检查


我的理由是,对于那些不应该发生的事情,应该保留例外情况。如果他们这样做了,就应该敲响警钟,把卡尔弗利带进来。对我来说,在已知的问题案例处理中使用异常似乎很奇怪,尤其是当您可以测试它时。

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

.ContainsKey检查每次都会让您损失一点性能,而抛出的异常很少会让您损失一点性能。如果复制的可能性确实很低,那么就允许例外


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

中的MultiDictionary,我尽量避免在任何地方出现异常-它们处理起来很昂贵,而且会使代码复杂化。既然您知道可能会发生冲突,而且执行.Contains检查很简单,我会这样做。

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

您可以使用AddorUpdate()方法扩展字典:


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

对于那些不应该发生的事情,应该保存例外 发生如果他们这样做了,就应该敲响警钟,把加略山带来 在里面对我来说,在已知问题案例中使用异常似乎很奇怪 处理,尤其是当你可以测试它的时候

这是有意义的,因为您通过使用
try catch
(如果有的话)获得的性能增益是微不足道的,但是“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>();
50%副本: 结果:

无重复项

方法1:调试->630毫秒-680毫秒;释放->620毫秒-640毫秒

方法2:调试->640毫秒-690毫秒;释放->640毫秒-670毫秒

50%的副本

方法1:调试->26毫秒-39毫秒;释放->25毫秒-33毫秒

方法2:调试->1340毫秒;释放->1260毫秒

100%重复

方法1:调试->7毫秒;释放->7毫秒

方法2:调试->2600毫秒;释放->2400毫秒


您可以看到,随着重复项的增加,
try-catch
的性能很差。即使在最糟糕的情况下,您根本没有复制品,
try-catch
的性能提升也没有什么实质性的提高。

有两种方法来看待这一点

演出 如果从性能角度来看,您必须考虑:

  • 计算密钥和
  • 创建和抛出异常的代价有多大
我想不出比抛出异常更昂贵的哈希计算了。请记住,当抛出异常时,它们已通过互操作封送到Win32 API、创建、爬网整个堆栈跟踪,并停止和处理遇到的任何catch块。CLR中的异常抛出仍然被视为由Win32 API在幕后处理的HRESULT。发件人:

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

性能约束:避免异常


最佳做法 .NET Framework设计指南是一个很好的遵循规则集(很少有例外-双关语)。指南中提到了一种称为“尝试/实干家”的模式,在这种情况下,建议避免例外情况:

考虑成员的Tester-Doer模式,该模式可能在常见场景中引发异常,以避免与异常相关的性能问题

异常也不应该被用作控制流机制——同样写在CLR设计指南中


最佳实践结论:避免例外

对于.NET标准2.1+和.NET核心2.0+来说,这个问题现在要简单得多。就用这个方法吧。它以最优雅的方式处理所有问题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);  
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);  
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);