这本C#字典的意义是什么&燃气轮机;优化?

这本C#字典的意义是什么&燃气轮机;优化?,c#,C#,我用的是C#4.0。Visual Studio中的“优化代码”已启用 在类中考虑以下代码: Dictionary<int, int> dictionary = new Dictionary<int, int>(); public void IncrementDictionary(int key) { if (!dictionary.ContainsKey(key)) { dictionary[key] = 1; } else {

我用的是C#4.0。Visual Studio中的“优化代码”已启用

在类中考虑以下代码:

Dictionary<int, int> dictionary = new Dictionary<int, int>();

public void IncrementDictionary(int key) {
    if (!dictionary.ContainsKey(key)) {
        dictionary[key] = 1;
    } else {
        dictionary[key]++;
    }
}
注意:在实际的生产代码中,使用此选项,优化器/编译器还会创建:
int-key2=key
并在最后一行中使用
键2

好的,
var
已被
Dictionary
替换,这是预期的。而
if
语句被简化为添加
return
,而不是使用
else


但是为什么要对原始词典进行新的引用呢?

我猜这可能是为了避免出现竞争条件,如果您有:

dictionary[i]=dictionary[i]+1

它不是原子的。指定给的
词典
在您获得值并递增后可能会更改

想象一下这个代码:

public Dictionary<int, int> dictionary = new Dictionary<int, int>();

public void Increment()
{
    int newValue = dictionary[0] + 1;
    //meanwhile, right now in another thread: dictionary = new Dictionary<int, int>();
    dictionary[0] = newValue; //at this point, "dictionary" is actually pointing to a whole new instance
}
LINQPad报告的IL为:

IL_0000:  nop         
IL_0001:  ldarg.0     
IL_0002:  ldfld       UserQuery.dictionary
IL_0007:  dup         
IL_0008:  stloc.0     
IL_0009:  ldc.i4.0    
IL_000A:  ldloc.0     
IL_000B:  ldc.i4.0    
IL_000C:  callvirt    System.Collections.Generic.Dictionary<System.Int32,System.Int32>.get_Item
IL_0011:  ldc.i4.1    
IL_0012:  add         
IL_0013:  callvirt    System.Collections.Generic.Dictionary<System.Int32,System.Int32>.set_Item
IL_0018:  nop         
IL_0019:  ret         
IL_0000:nop
IL_0001:ldarg.0
IL_0002:ldfld UserQuery.dictionary
IL_0007:dup
IL_0008:stloc.0
IL_0009:ldc.i4.0
IL_000A:ldloc.0
IL_000B:ldc.i4.0
IL_000C:callvirt System.Collections.Generic.Dictionary.get_项
IL_0011:ldc.i4.1
IL_0012:添加
IL_0013:callvirt System.Collections.Generic.Dictionary.set_项
IL_0018:没有
IL_0019:ret
我不完全确定(我不是IL wiz),但我认为
dup
调用本质上是将堆栈上的同一字典引用加倍,因此不管如何,get和set调用都将指向同一字典。也许这就是ILSpy将其表示为C#代码的方式(至少在行为上是相同的)。我想。如果我错了,请纠正我,因为,就像我说的,我还不知道我喜欢我的手背


编辑:必须运行,但最后的要点是:
++
++=
不是原子操作,实际上执行的指令比C#中描述的要复杂得多。因此,为了确保在同一个字典实例上执行每个get/increment/set步骤(正如您从C#代码中期望和要求的那样),需要对字典进行本地引用,以避免两次运行字段“get”操作,这可能导致指向新实例。ILSpy如何描述索引+=操作所涉及的所有工作取决于它。

您的编辑弄乱了您试图显示的内容,但原因是
字典[key]++
临时复制
字典
是因为它无法知道
字典
的索引getter是否会更改字段
字典
。该规范指出,即使在索引get期间更改了字段
字典
,索引put仍将在同一对象上执行


顺便说一句,.net确实应该(现在仍然应该)提供一种方法,通过这种方法类可以将属性公开为
ref
Dictionary
可以提供方法
ActOnValue(TKey-key,ActionByRef-action)
ActOnValue(TKey-key,ActionByRef-action,ref-TParam-param)
(假设
ActionByRef
类似于
action
,但参数声明为
ref
)。如果是这样,就可以对集合对象执行读-修改-写操作,而无需索引到集合中两次。不幸的是,没有以这种方式公开属性的标准约定,也没有任何语言支持。

重要提示:没有创建第二个字典。相反,第二次引用了现有的字典。@Jon:是的,我错了。你发布的ILSpy不是CIL代码中发生的事情的精确表示。这可能是CIL-to-C反编译器中的错误或缺陷。你能发布CIL以便我们确定吗?@SimpleCoder用Reflector反编译了它,看起来几乎一样,但它保留了
其他
@SimpleCoder我想你可能混淆了你的代码块:它们都显示了ILSpy结果。:)现在比较起来有点难!不,
dictionary2
仍指向原始字典。对
Increment()
控制
dictionary2
:它是对原始字典的本地引用。您关于不两次解析属性getter的观点非常相关,实际上不是优化,而是调用dictionary[key]++时直观地期望的结果(即,如果属性有一些副作用,它应该只发生一次)
…我们开始增加值…
实际上,不会
(dictionary2=dictionary)
执行之前,因为它在括号中?@ChrisSinclair:即使在单线程的情况下,编译器也无法知道
字典
“index get”函数是否会更改任何对象的任何字段(包括
this.Dictionary
)。这不完全是“竞争条件”,但这是个问题。好的,谢谢。你说的很有道理——我从来没有想过。
public void IncrementFix()
{
    var dictionary2 = dictionary;
    //in another thread: dictionary = new Dictionary<int, int>();
    //this is OK, dictionary2 is still pointing to the ORIGINAL dictionary
    int newValue = dictionary2[0] + 1;
    dictionary2[0] = newValue;
}
public void IncrementDictionary() 
{
    dictionary[0]++;
}
IL_0000:  nop         
IL_0001:  ldarg.0     
IL_0002:  ldfld       UserQuery.dictionary
IL_0007:  dup         
IL_0008:  stloc.0     
IL_0009:  ldc.i4.0    
IL_000A:  ldloc.0     
IL_000B:  ldc.i4.0    
IL_000C:  callvirt    System.Collections.Generic.Dictionary<System.Int32,System.Int32>.get_Item
IL_0011:  ldc.i4.1    
IL_0012:  add         
IL_0013:  callvirt    System.Collections.Generic.Dictionary<System.Int32,System.Int32>.set_Item
IL_0018:  nop         
IL_0019:  ret