我正在向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调用,因为集合很小,而且代码更具可读性。
答案 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);
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);
结果:
没有重复
方法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)
您可以使用AddorUpdate()方法扩展Dictionary:
http://williablog.net/williablog/post/2011/08/30/Generic-AddOrUpdate-Extension-for-IDictionary.aspx
答案 6 :(得分:0)
有两种方法来看待这个......
如果您从性能角度考虑它,则必须考虑:
我无法想到任何比抛出异常更昂贵的哈希计算。请记住,当抛出异常时,它们必须跨接到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检查是微不足道的,我会这样做。