堆上或堆栈中的引用类型混淆了吗?

时间:2014-07-31 10:09:12

标签: c# pass-by-reference

只是为了了解引用类型的内容以及基于Eric Lippert http://blogs.msdn.com/b/ericlippert/archive/2009/05/04/the-stack-is-an-implementation-detail-part-two.aspx的文章,我想深入理解为什么这段代码会像这样工作

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication4
{
    class Program
    {
        static void Main(string[] args)
        {
            MyCalleeClass calleeClass =  new MyCalleeClass();
            calleeClass.MyTestProp = "toto";
            MyCallerClass  callerClass = new MyCallerClass();
            callerClass.TestMethod(calleeClass);
            //normally given  that a class it's a reference type this should give a toto 1  
            Console.WriteLine(calleeClass.MyTestProp);


        }
    }

    class MyCalleeClass
    {
        public string MyTestProp { get; set; }
    }

    class MyCallerClass
    {

    public void  TestMethod(MyCalleeClass calleeClass)
    {
           Console.WriteLine("In the caller method");           
           //does this object is created on  the stack or on the heap 
           calleeClass = new MyCalleeClass();
           calleeClass.MyTestProp = "toto 1"; 
           Console.WriteLine(calleeClass.MyTestProp);


    }
    }
}

另一个问题是,如果没有在堆栈上创建testMethod中的caleeClass,那么会出现一个可以在堆栈上创建引用类型的特殊情况

3 个答案:

答案 0 :(得分:3)

某事物是“实施细节”这一事实意味着实际的实施不是您应该知道的事情。

首先让我们处理细节:

  • 构造引用类型的对象时,该对象存在于堆
  • 但是,参考文献可以存在很多地方:
    • 作为堆栈中的临时元素
    • 作为堆栈中的变量
    • 作为值类型的字段(而不是它存在于值类型所在的任何地方)
    • 作为引用类型的字段(由于引用类型位于堆上,其中的字段也是如此)

但是这个问题实际上似乎混淆了以下两个概念:

  • 传递参​​考类型参数
  • 通过引用传递参数

这是两个不同的东西,对于你的问题,正确的陈述是你正在通过值传递引用类型参数。

这一切都开始让很多人感到困惑所以让我们试着看看这里发生了什么。

实际上,参考只是一个数字。它是指内存中其他对象的东西。很可能该数字是该对象的地址(在内存中)。

所以,在你构建了第一个对象之后:

MyCalleeClass calleeClass =  new MyCalleeClass();
calleeClass.MyTestProp = "toto";

假设calleeClass包含数字(引用)1234.在地址1234处,存在类型为MyCalleeClass的对象,该对象的MyTestProp属性具有值“toto ”

好的,然后将此引用传递给该方法。基本上,您为该方法提供参考1234 的副本。

在该方法中,您构造另一个对象并将对该对象的引用分配给相同的局部变量(参数),覆盖 1234引用,例如5678指着你的新物体。

现在你改变那个对象的属性,即新的对象。

然后返回外部代码。由于该代码为方法提供了引用1234的副本引用仍然具有1234并指向原始对象,在属性中带有“toto”。

这是按值传递引用的含义,您为方法提供参考值的副本。正在调用的代码仍然具有原始引用。

如果您希望调用的代码继续使用 new 引用,则需要通过引用传递引用。这也令人困惑,因为这里的两个“参考”词实际上意味着不同的东西。

  • 引用是对象的引用
  • 通过引用引用意味着将访问传递给基础变量,而不仅仅是其值的副本

TL; DR 您的代码行为方式,因为您在外部使用一个对象,并在内部构造和更改新对象,但外部世界不会回来那个新对象并继续使用旧对象。

至于你的第二个问题,可以在堆栈上分配引用类型,然后是否。所有对象都在托管堆上分配。

你可以在不安全的代码中分配堆栈上看起来像引用类型的东西,基元/值类型的数组,如下所示:

unsafe void Test()
{
    int* values = stackalloc int[10];
}

但这不是“数组引用类型”,它只是指向堆栈上分配的10个int值中的第一个的指针,所以它不是一回事。

答案 1 :(得分:0)

这可以变成一个大讨论。

TestMethod生成MyCalleeClass  因为这是一个类(引用类型),所以它在堆上生成(比如在地址1000) 您的堆栈将包含指向地址1000的指针。

在地址1000处,您将获得对您的类中的字符串的另一个引用(字符串也是一个ref。类型,它将位于地址2000中 - 对于此示例)。

所以:

TestMethod的堆栈将有一个指向地址1000的指针 在地址1000中,您将有一个指向地址2000的指针 在地址2000中,您将有一个字符串。

答案 2 :(得分:0)

在你的主要:

callerClass.TestMethod(calleeClass) - >对calleeClass的引用被复制为参数

在你的TestMethod中

calleeClass = new MyCalleeClass - >复制的引用(方法的参数)被覆盖以引用另一个对象

当返回TestMethod调用时,不会复制引用,因此您仍在引用main中的原始类。

如果您确实需要您建议的行为,则应指定:TestMethod( ref MyCalleeClass calleeClass)。