【问题标题】:C# Modifying Reference Type Objects doesn't reflect changesC# 修改引用类型对象不反映更改
【发布时间】:2018-04-09 18:02:35
【问题描述】:

我有这个 C# 代码:

int i = 20;

object t = i;

object r = t;

r = 100;

为什么此时T的值还是20而不是100?

我认为 T 和 R 指向同一个位置,并且对它们中的任何一个的更改都应该相互影响......

我不太明白引用类型在堆中是如何工作的,请帮助我

【问题讨论】:

    标签: c# object types reference


    【解决方案1】:

    为什么此时 T 的值还是 20 而不是 100?

    因为你没有修改t

    您需要查找"boxing"。但是,您的代码中发生的情况是,存储在 i 中的值 20 是“装箱”的,这意味着分配了一个新的引用类型对象,并将值 20 复制到该对象中。

    当您分配r = t 时,您将该框值的引用复制到t。到目前为止,一切顺利。

    但是,当您分配r = 100; 时,您没有修改了装箱的值。原来的装箱值保持不变,但现在仅由t 引用。

    赋值r = 100 创建一个全新的装箱值,分配在堆上,并将对该对象的引用复制到变量r 中。这对t 没有影响,它仍然设置为20 的第一个装箱值的引用。

    【讨论】:

    • 盒装值类型不是不可变的。它们可以提供改变装箱值的方法,尽管它们中的大多数不这样做。
    • @PetSerAl:有趣。感谢您澄清这一点。我忽略了这种可能性,因为通常通过System.Object 引用一个装箱值,这当然不会暴露装箱对象的任何底层功能。我没有想到可以访问该值,除了将其拆箱,更不用说修改它了。但一项快速测试表明,通过反射,实际上可以访问装箱值类型的成员并实际操作它。而且我认为内部机制(例如运行计时器)可以做同样的事情。我将进行编辑以修复该错误。
    • 其实我在写这篇文章的时候并没有考虑反思。无需拆箱即可调用接口方法。例如:在盒装 List<T>.Enumerator 上调用 IEnumerator.MoveNext 会将盒装枚举器推进到下一项。
    • @PetSerAl:是的,这是进入装箱对象的另一条途径。有趣的是,我跟进了我的“运行计时器”场景,发现可变值类型中的 async 方法可能是错误的 (*)。也就是说,因为生成的状态引擎会为每个 await 创建一个值的新副本,如果一个从例如启动计时器装箱值的类型中的接口方法,保存对原始装箱值的引用的代码不会看到任何更改。如果操作在单个线程中完成(例如 Thread.Sleep() 更改值),则该场景可以正常工作。
    • (*) 也就是说,首先比可变值类型更糟糕。 :)
    【解决方案2】:

    当您将整数值i 设置为对象t 时,会发生装箱并将盒子内的整数值放入堆中。

    当您将t 设置为r = t 时,此时它们指向堆上的相同引用地址。但是当你用一个新的整数值设置rr = 100 时,会发生新的装箱并且r 将指向一个不同的地址。

    首先,装箱和拆箱是非常昂贵的事情。为什么要尝试将整数设置为对象?

    如果您想将整数传递给某个方法并获得更改的值,您可以在方法参数中使用ref 关键字。

    您可以在下面找到示例代码:

        public int GetValue() {
            int i = 20;
            SetNewValue(ref i);
            return i;
        }
    
        public void SetNewValue(ref int value) {
            value = 100;
        }
    

    【讨论】:

    • 首先,感谢您的澄清。特别是出于精确原因,我并没有尝试将整数设置为对象。这只是一个用于测试目的的示例代码,因为我拼命地试图理解引用类型是如何工作的,直到你的好例子我才能得到它!
    猜你喜欢
    • 2016-04-15
    • 2014-12-19
    • 2017-10-21
    • 1970-01-01
    • 1970-01-01
    • 2013-06-26
    • 1970-01-01
    • 1970-01-01
    • 2014-11-26
    相关资源
    最近更新 更多