为什么在复制之后原始对象发生了变化,而不使用ref参数?

时间:2014-09-26 15:25:12

标签: c# asp.net reference

在工作中,我们遇到了一个问题,即我们在通过方法发送副本后更改了原始对象。我们确实在原始课程中使用IClonable找到了解决方法,但我们无法在第一时间找出原因。

我们编写了这个示例代码来重现问题(类似于我们的原始代码),并希望有人能够解释它为什么会发生。

public partial class ClassRefTest : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        var myclass = new MyClass();
        var copy = myclass;
        myclass.Mystring = "jadajadajada";
        Dal.DoSomeThing(copy);

        lit.Text = myclass.Mystring; //Text is expected to be jadajadajada, 
                                       but ends up to be referenced
    }
}

public class MyClass
{
    public string Mystring { get; set; } 
}

public static class Dal
{
    public static int? DoSomeThing(MyClass daclass)
    {
        daclass.Mystring = "referenced";
        return null;
    }

}

正如您所看到的,在DoSomething()方法中我们没有使用任何ref参数,但lit.Text最终仍然被引用。

为什么会这样?

4 个答案:

答案 0 :(得分:7)

解释这是如何工作总是很有趣。当然,我的解释不能与Jon Skeet oneJoseph Albahari的辉煌相提并论,但不过我会尝试。

在C编程的旧时代,掌握指针的概念是使用该语言的基础。这么多年过去了,现在我们称它们为引用,但它们仍然是......美化的指针,如果你了解它们是如何工作的,你就成了程序员的一半(开玩笑)

什么是参考?我会告诉你一个非常简短的回答。它是存储在变量中的数字,此数字表示数据所在的内存中的地址 为什么我们需要参考?因为处理单个数字非常简单,我们可以通过这个数字读取数据的内存区域,而不是将整个对象及其所有字段与我们的代码一起移动。

那么,当我们写

时会发生什么
var myclass = new MyClass();

我们都知道这是对类MyClass的构造函数的调用,但是对于Framework,它也是一个提供内存区域的请求,其中实例的值(属性,字段和其他内部)家政信息)生活和存在于特定的时间点。假设MyClass需要100个字节来存储它需要的一切。框架以某种方式搜索计算机内存,并且假设它在由地址4200标识的内存中找到一个位置。该值(4200)是它分配给var myclass的值。指向内存的指针(oops它是对象实例的引用)

现在打电话会发生什么?

var copy = myclass;

没什么特别的。 copy变量的值myclass(4200)相同。但是这两个变量引用了相同的内存区域,因此使用其中一个或另一个并没有任何区别。内存区域(MyClass的实例)仍然位于我们虚构的内存地址4200。

myclass.Mystring = "jadajadajada";

它使用引用值作为基值来查找属性占用的内存区域,并将其值设置为保留文字字符串的intern区域。如果我可以用指针进行类比,就像你取基本内存(4200)一样,添加一个偏移量来找到表示属性MyString的引用保持在我们的对象实例占用的100字节边界内的点。让我们说MyString引用是超过内存区域开头的42个字节。添加42到4200 yelds 4242,这是对文字" jadajadajada"的引用。将被存储。

Dal.DoSomeThing(copy);

这里有问题(你遇到问题的地方)。当您传递copy变量时,不要认为框架会重复搜索内存区域并复制新区域中原始区域的所有内容。不,这几乎是不可能的(想想MyClass是否包含属性是另一个类的实例等等......它永远不会停止。)因此传递给DoSomeThing方法的值再次成为参考值4200.此值自动分配给声明为daclass的输入参数的局部变量DoSomething(就像您之前使用var copy = myclass;明确指出的那样。

此时很明显,使用daClass的任何操作都会对原始实例占用的内存区域起作用,并且当代码返回到起始点时,您会看到结果。

请原谅技术专家的用户赦免。特别是对于我随意和不精确地使用术语“记忆地址”而言。

答案 1 :(得分:3)

这是正常的,因为你的MyClass是一个引用类型,所以你传递一个对原始数据的引用而不是数据本身,这就是为什么它是一个预期的行为 这里是对Parameter passing in C#

中引用类型的解释
  

引用类型是一种类型,其值为对适当数据的引用,而不是数据本身

答案 2 :(得分:1)

MSDN的文档非常清楚地说明了这一点。默认情况下,值类型作为副本传递,默认情况下将对象作为引用传递。 Methods in C#

答案 3 :(得分:1)

我在这里看到两个问题......

制作对象的副本

var copy = myClass;没有复制 - 它真正做的是为myClass创建第二个引用(“指针”)(命名变量“copy”具有误导性)。所以你有myClass并复制指向同一个确切的对象。

要制作副本,您必须执行以下操作:

var copy = new MyClass(myClass);

请注意,我创建了一个新对象。

以参考方式传递

  1. 在没有ref的情况下传递值类型变量时,接收方法无法更改变量。

    示例:DoSomething(int foo) - DoSomething不会影响foo的值。

  2. 使用ref传递值类型变量时,可以更改变量

    示例:DoSomething(ref int foo) - 如果DoSomething更改foo,它将保持更改。

  3. 传递没有引用的对象时,可以更改对象的数据,但不能更改对象的引用。

  4. void DoSomething(MyClass myClass)
    {
        myClass.myString = "ABC"   // the string is set to ABC
        myClass = new MyClass();   // has no affect - or may not even be allowed
    }
    
    1. 使用ref传递对象时,可以更改对象的数据,并可以更改对象的引用。
    2. void DoSomething(ref MyClass myClass)
      {
          myClass.myString = "ABC"   // the string is set to ABC
          myClass = new MyClass();   // the string will now be "" since myClass has been changed
      }