在C#中,为什么dictionary [0] ++有效?

Mik*_*ike 20 .net c# dictionary

考虑以下C#代码:

var d = new Dictionary<int, int>();
d[0] = 0;
d[0]++;
Run Code Online (Sandbox Code Playgroud)

执行此代码后d [0]的值是多少?我希望d [0] == 0,因为Dictionary <>的Item属性返回一个值类型 int,可能在堆栈上,然后递增.然而,令人惊讶的是,当您实际运行此代码时,您会发现d [0] == 1.

上面的示例表现得好像索引器正在返回引用类型,但现在考虑以下内容:

var d = new Dictionary<int, int>();
d[0] = 0;
var a = d[0];
a++;
Run Code Online (Sandbox Code Playgroud)

执行此代码后d [0]的值是多少?这次我们按预期得到d [0] == 0,所以索引器肯定没有返回引用.

有谁知道为什么我们看到这种行为?

use*_*407 19

C#规范7.6.9后缀增量和减量运算符:

x ++或x--形式的后缀增量或减量操作的运行时处理包括以下步骤:

  • 如果x被归类为属性或索引器访问:
    • 将评估实例表达式(如果x不是静态的)和与x关联的参数列表(如果x是索引器访问),并将结果用于后续的get和set访问器调用.
    • 调用x的get访问器并保存返回的值.
    • 调用所选运算符,并将保存的值x作为其参数.
    • 使用运算符返回的值作为其value参数调用x的set访问器.
    • x的保存值成为操作的结果.

这实际上与值类型与引用类型语义无关,因为--并且++不应该更改实例但返回具有新值的新实例.

public static class Test {
    public static void Main() {
        TestReferenceType();
        TestValueType();
    }
    public static void TestReferenceType() {
        var d=new Dictionary<int,BoxedInt>();
        BoxedInt a=0;
        d[0]=a;
        d[0]++;
        d[1]=2;
        BoxedInt b=d[1];
        b++;
        Console.WriteLine("{0}:{1}:{2}:{3}",a,d[0],d[1],b);
    }
    public static void TestValueType() {
        var d=new Dictionary<int,int>();
        int a=0;
        d[0]=a;
        d[0]++;
        d[1]=2;
        int b=d[1];
        b++;
        Console.WriteLine("{0}:{1}:{2}:{3}",a,d[0],d[1],b);
    }
    public class BoxedInt {
        public int Value;
        public BoxedInt(int value) {
            Value=value;
        }
        public override string ToString() {
            return Value.ToString();
        }
        public static implicit operator BoxedInt(int value) {
            return new BoxedInt(value);
        }
        public static BoxedInt operator++(BoxedInt value) {
            return new BoxedInt(value.Value+1);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这两种测试方法将打印相同的字符串0:1:2:3.如您所见,即使使用引用类型,您也必须调用set accessor来观察字典中的更新值.


rex*_*ghk 5

您的代码以这种方式工作,因为的索引d返回值 参考的int(这是值类型)。您的代码基本上与此相同:

var d = new Dictionary<int, int>();
d[0] = 0;
d[0] = d[0] + 1; // 1. Access the indexer of `d`
                 // 2. Increment it (returned another int, which is of course a value type)
                 // 3. Store the new int to d[0] again (d[0] now equals to 1)
Run Code Online (Sandbox Code Playgroud)

d[0]0在第二个代码示例中返回的原因是由于值类型的语义,特别是以下行:

var a = d[0]; // a is copied by value, not a **reference** to d[0], 
             // so they are two separate integers from here
a++; // a is incremented, but d[0] is not, because they are **two** separate integers
Run Code Online (Sandbox Code Playgroud)

我发现Jon Skeet 对引用类型和值类型的区别的解释非常有帮助。

  • 为什么会这样?您正在将值类型数据的副本复制到变量a中-没有理由改变d [0]的值。 (2认同)
  • d [0]不是对int的引用。索引的行为类似于属性,因此它基本上是对get / set的函数调用。 (2认同)
  • 不是返回参考,这就是为什么OP看到令他困惑的行为的原因。您的第一句话是完全错误的。 (2认同)