本文介绍了为什么在复制后更改原始对象,而不使用 ref 参数?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在工作中,我们遇到了一个问题,在我们通过方法发送副本后,原始对象发生了变化.我们确实通过在原始类中使用 IClonable 找到了一种解决方法,但因为我们无法找出它首先发生的原因.

At work we were encountering a problem where the original object was changed after we send a copy through a method. We did find a workaround by using IClonable in the original class, but as we couldn't find out why it happened in the first place.

我们编写此示例代码是为了重现该问题(类似于我们的原始代码),希望有人能够解释为什么会发生这种情况.

We wrote this example code to reproduce the problem (which resembles our original code), and hope someone is able to explain why it happens.

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最终被引用.

As you can see, in the DoSomething() method we're not using any ref argument, but still the lit.Text ends up to be referenced.

为什么会发生这种情况?

Why does this happen?

推荐答案

解释它是如何工作的总是很有趣的.当然,我的解释无法与 Jon Skeet oneJoseph Albahari,但我还是会尝试.

It is always interesting to explain how this works. Of course my explanation could not be on par with the magnificiency of the Jon Skeet one or Joseph Albahari, but I would try nevertheless.

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

In the old days of C programming, grasping the concept of pointers was fundamental to work with that language. So many years are passed and now we call them references but they are still ... glorified pointers and, if you understand how they work, you are half the way to become a programmer (just kidding)

什么是引用?在一个非常简短的回答中,我会告诉.它是一个存储在变量中的数字,这个数字代表数据所在的内存地址.
为什么我们需要参考?因为处理单个数字非常简单,我们可以用它来读取数据的内存区域,而不是让整个对象的所有字段都随我们的代码一起移动.

What is a reference? In a very short answer I would tell. It is a number stored in a variable and this number represent an address in memory where your data lies.
Why we need references? Because it is very simple to handle a single number with which we could read the memory area of our data instead of having a whole object with all its fields moved along with our code.

那么,当我们写的时候会发生什么

So, what happens when we write

var myclass = new MyClass();

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

We all know that this is a call to the constructor of the class MyClass, but for the Framework it is also a request to provide a memory area where the values of the instance (property, fields and other internal housekeeping infos) live and exist in a specific point in time. Suppose that MyClass needs 100 bytes to store everything it needs. The framework search the computer memory in some way and let's suppose that it finds a place in memory identified by the address 4200. This value (4200) is the value that it is assigned to the var myclass It is a pointer to the memory (oops it is a reference to the object instance)

现在当你打电话时会发生什么?

Now what happens when you call?

var copy = myclass;

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

Nothing particular. The copy variable gets the same value of myclass (4200). But the two variables are referencing the same memory area so using one or the other doesn't make any difference. The memory area (the instance of MyClass) is still located at our fictional memory address 4200.

myclass.Mystring = "jadajadajada";

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

This uses the reference value as a base value to find the area of memory occupied by the property and sets its value to the intern area where the literal strings are kept. If I could make an analogy with pointers it is as you take the base memory (4200), add an offset to find the point where the reference representing the propery MyString is kept inside the boundaries of the 100 bytes occupied by our object instance. Let's say that the MyString reference is 42 bytes past the beginning of the memory area. Adding 42 to 4200 yelds 4242 and this is the point in which the reference to the literal "jadajadajada" will be stored.

Dal.DoSomeThing(copy);

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

Here the problem (well the point where you have the problem). When you pass the copy variable don't think that the framework repeat the search for a memory area and copy everything from the original area in a new area. No, it would be practically impossible (think about if MyClass contains a property that is an instance of another class and so on... it could never stop.) So the value passed to the DoSomeThing method is again the reference value 4200. This value is automatically assigned to the local variable daclass declared as the input parameter for DoSomething (It is like you have explicitly done before with var copy = myclass;.

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

At this point it is clear that any operation using daClass acts on the same memory area occupied by the original instance and you see the results when code returns back to your starting point.

请多多指教这里的技术专家.特别是我对内存地址"这个术语的随意和不精确的使用.

I beg the pardon from the more technically expert users here. Particularly for my casual and imprecise use of the term 'memory address'.

这篇关于为什么在复制后更改原始对象,而不使用 ref 参数?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-01 02:07