这个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 {
        dictionary[key]++;
    }
}

在这里,对IncrementDictionary的调用有两件事情之一:

>如果key没有值,则创建一个值并将其初始化为1.
>如果存在值,则该值增加1.

现在看看当我使用ILSpy反编译结果时会发生什么:

Dictionary<int,int>();

public void IncrementDictionary(int key) {
    if (!dictionary.ContainsKey(key)) {
        dictionary[key] = 1;
        return;
    }
    Dictionary<int,int> dictionary2;
    (dictionary2 = dictionary)[key] = dictionary2[key] + 1;
}

注意:在使用这个的实际生产代码中,优化器/编译器也创建:int key2 = key;并在最后一行使用key2.

好的,var已经被Dictionary< int,int&gt而if语句被简化为添加一个return而不是使用else. 但是为什么这个新的引用是原始字典创建的?

解决方法

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

dictionary [i] = dictionary [i] 1

这不是原子的.分配给您的字典可以在您获得该值并递增之后更改.

想象这个代码

public 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
}

使用它们具有的局部变量赋值,它看起来更像是这样来避免条件:

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;
}

请注意,它不能完全满足所有线程安全的要求.例如,在这种情况下,我们开始增加值,但类中的字典引用已更改为全新的实例.但是,如果您需要更高级别的线程安全性,则需要实现自己的积极的同步/锁定,这通常不在编译器优化范围之内.但是,从我能够说出来的这一点,并没有给处理带来任何大的打击(如果有的话),并且避免了这种情况.这可能是特殊情况,如果字典是一个属性不是一个字段,因为它在你的例子,这绝对是一个优化,不解析属性getter两次. (任何机会,您的实际代码是否使用字典的属性而不是您发布的字段?)

编辑:嗯,一个简单的方法

public void IncrementDictionary() 
{
    dictionary[0]++;
}

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 wiz),但是我认为dup调用在堆栈上基本上加倍了相同的字典引用,所以无论get和set调用将指向同一个字典.也许这就是ILSpy如何将其表示为C#代码(至少与行为一样,它或多或少相同).我认为.如果我错了,请纠正我,因为像我说的那样,我不知道我的手背.

编辑:运行,但最后的要点是:和=不是原子操作,实际上在执行指令中比C#中描述的更复杂.因此,为了确保每个get / increment / set步骤都在相同的字典实例上执行(就像C#代码所要求的那样),对字典进行本地引用,以避免运行字段“获得“操作两次,这可能导致指向一个新的实例. ILSpy如何描述所有涉及索引=操作的工作都是由它完成的.

猜你在找的C#相关文章