【发布时间】:2018-04-09 18:02:35
【问题描述】:
我有这个 C# 代码:
int i = 20;
object t = i;
object r = t;
r = 100;
为什么此时T的值还是20而不是100?
我认为 T 和 R 指向同一个位置,并且对它们中的任何一个的更改都应该相互影响......
我不太明白引用类型在堆中是如何工作的,请帮助我
【问题讨论】:
我有这个 C# 代码:
int i = 20;
object t = i;
object r = t;
r = 100;
为什么此时T的值还是20而不是100?
我认为 T 和 R 指向同一个位置,并且对它们中的任何一个的更改都应该相互影响......
我不太明白引用类型在堆中是如何工作的,请帮助我
【问题讨论】:
为什么此时 T 的值还是 20 而不是 100?
因为你没有修改t。
您需要查找"boxing"。但是,您的代码中发生的情况是,存储在 i 中的值 20 是“装箱”的,这意味着分配了一个新的引用类型对象,并将值 20 复制到该对象中。
当您分配r = t 时,您将该框值的引用复制到t。到目前为止,一切顺利。
但是,当您分配r = 100; 时,您没有修改了装箱的值。原来的装箱值保持不变,但现在仅由t 引用。
赋值r = 100 创建一个全新的装箱值,分配在堆上,并将对该对象的引用复制到变量r 中。这对t 没有影响,它仍然设置为20 的第一个装箱值的引用。
【讨论】:
System.Object 引用一个装箱值,这当然不会暴露装箱对象的任何底层功能。我没有想到可以访问该值,除了将其拆箱,更不用说修改它了。但一项快速测试表明,通过反射,实际上可以访问装箱值类型的成员并实际操作它。而且我认为内部机制(例如运行计时器)可以做同样的事情。我将进行编辑以修复该错误。
List<T>.Enumerator 上调用 IEnumerator.MoveNext 会将盒装枚举器推进到下一项。
async 方法可能是错误的 (*)。也就是说,因为生成的状态引擎会为每个 await 创建一个值的新副本,如果一个从例如启动计时器装箱值的类型中的接口方法,保存对原始装箱值的引用的代码不会看到任何更改。如果操作在单个线程中完成(例如 Thread.Sleep() 更改值),则该场景可以正常工作。
当您将整数值i 设置为对象t 时,会发生装箱并将盒子内的整数值放入堆中。
当您将t 设置为r = t 时,此时它们指向堆上的相同引用地址。但是当你用一个新的整数值设置r 像r = 100 时,会发生新的装箱并且r 将指向一个不同的地址。
首先,装箱和拆箱是非常昂贵的事情。为什么要尝试将整数设置为对象?
如果您想将整数传递给某个方法并获得更改的值,您可以在方法参数中使用ref 关键字。
您可以在下面找到示例代码:
public int GetValue() {
int i = 20;
SetNewValue(ref i);
return i;
}
public void SetNewValue(ref int value) {
value = 100;
}
【讨论】: