.net:添加字典项-检查它是否存在或允许异常?
我正在向StringDictionary添加项,可能会出现重复键。这当然会引发异常 如果重复的可能性很低(即很少发生),我是否最好使用Try-Catch块并保持其未处理状态,还是应该在添加每个条目之前始终进行.ContainsKey检查 我假设如果重复密钥的可能性很高,那么允许异常将是一个糟糕的决定,因为它们很昂贵 想法 编辑 我在通用字典中使用了reflector,并为ContainsKey和TryGetValue找到了以下内容,如下所述.net:添加字典项-检查它是否存在或允许异常?,.net,dictionary,.net,Dictionary,我正在向StringDictionary添加项,可能会出现重复键。这当然会引发异常 如果重复的可能性很低(即很少发生),我是否最好使用Try-Catch块并保持其未处理状态,还是应该在添加每个条目之前始终进行.ContainsKey检查 我假设如果重复密钥的可能性很高,那么允许异常将是一个糟糕的决定,因为它们很昂贵 想法 编辑 我在通用字典中使用了reflector,并为ContainsKey和TryGetValue找到了以下内容,如下所述 public bool TryGetValue(TKe
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
的性能提升也没有什么实质性的提高。有两种方法来看待这一点
演出
如果从性能角度来看,您必须考虑:
- 计算密钥和
- 创建和抛出异常的代价有多大
最佳做法 .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);