这个问题似乎是无稽之谈.该行为无法可靠再现.
比较以下测试程序,我观察到以下示例中第一个和第二个之间的巨大性能差异(第一个示例比第二个示例慢10倍):
第一个例子(慢):
interface IWrappedDict {
int Number { get; }
void AddSomething (string k, string v);
}
class WrappedDict : IWrappedDict {
private Dictionary<string, string> dict = new Dictionary<string,string> ();
public void AddSomething (string k, string v) {
dict.Add (k, v);
}
public int Number { get { return dict.Count; } }
}
class TestClass {
private IWrappedDict wrappedDict;
public TestClass (IWrappedDict theWrappedDict) {
wrappedDict = theWrappedDict;
}
public void DoSomething () {
// this function does the performance test
for (int i = 0; i < 1000000; ++i) {
var c = wrappedDict.Number; wrappedDict.AddSomething (...);
}
}
}
Run Code Online (Sandbox Code Playgroud)
第二个例子(快):
// IWrappedDict as above
class WrappedDict : IWrappedDict {
private Dictionary<string, string> dict = new Dictionary<string,string> ();
private int c = 0;
public void AddSomething (string k, string v) {
dict.Add (k, v); ++ c;
}
public int Number { get { return c; } }
}
// rest as above
Run Code Online (Sandbox Code Playgroud)
有趣地,差异消失(第一示例获取快以及)如果我改变构件变量的类型TestClass.wrappedDict从IWrappedDict到WrappedDict.我对此的解释是Dictionary.Count每次访问元素时重新计算元素,并且元素数量的潜在缓存仅由编译器优化完成.
任何人都可以证实吗?有没有办法以高效的方式获得元素的数量Dictionary?
不,每次使用时Dictionary.Count都不会重新计算元素.字典维护计数,应该与第二个版本一样快.
我怀疑在第二个例子的测试中,你已经有了,WrappedDict而不是IWrappedDict,这实际上是关于接口成员访问(总是虚拟的)和JIT在知道具体类型时编译对属性的内联调用.
如果您仍然认为Count是问题,您应该能够编辑您的问题以显示一个简短但完整的程序,该程序演示快速和慢速版本,包括您如何计时.
听起来你的时机不对;我得到:
#1: 330ms
#2: 335ms
Run Code Online (Sandbox Code Playgroud)
在 IDE 外部以发布模式运行以下命令时:
public void DoSomething(int count) {
// this function does the performance test
for (int i = 0; i < count; ++i) {
var c = wrappedDict.Number; wrappedDict.AddSomething(i.ToString(), "a");
}
}
static void Execute(int count, bool show)
{
var obj1 = new TestClass(new WrappedDict1());
var obj2 = new TestClass(new WrappedDict2());
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
GC.WaitForPendingFinalizers();
var watch = Stopwatch.StartNew();
obj1.DoSomething(count);
watch.Stop();
if(show) Console.WriteLine("#1: {0}ms", watch.ElapsedMilliseconds);
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
GC.WaitForPendingFinalizers();
watch = Stopwatch.StartNew();
obj2.DoSomething(count);
watch.Stop();
if(show) Console.WriteLine("#2: {0}ms", watch.ElapsedMilliseconds);
}
static void Main()
{
Execute(1, false); // for JIT
Execute(1000000, true); // for measuring
}
Run Code Online (Sandbox Code Playgroud)
基本上:“无法重现”。另外:为了完整性, no:.Count不会计算所有项目(它已经知道计数),编译器也不会添加任何神奇的自动缓存代码(注意:有一些有限的例子;例如,JIT可以删除向量for上循环的边界检查)。