CuccoChaser
CuccoChaser

Reputation: 1079

Why is the original object changed after a copy, without using ref arguments?

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;
    }

}

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?

Upvotes: 4

Views: 11708

Answers (4)

mdebeus
mdebeus

Reputation: 1928

I see two issues here...

Making a Copy of an object

var copy = myClass; does not make a copy - what it really does is create a second reference ("pointer") to myClass (naming the variable "copy" is misleading). So you have myClass and copy pointing to the same exact object.

To make a copy you have to do something like:

var copy = new MyClass(myClass);

Notice that I created a new object.

Passing By Reference

  1. When passing value type variables without ref, the variable cannot be changed by the the receiving method.

    Example: DoSomething(int foo) - DoSomething cannot affect the value of foo outside of itself.

  2. When passing value type variables with ref, the variable can be changed

    Example: DoSomething(ref int foo) - if DoSomething changes foo, it will remain changed.

  3. When passing an object without ref, the object's data can be changed, but the reference to the object cannot be changed.

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. When passing an object with ref, the object's data can be changed, and the reference to the object can be changed.
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
}

Upvotes: 3

Steve
Steve

Reputation: 216343

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.

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();

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;

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";

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);

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;.

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'.

Upvotes: 9

BRAHIM Kamel
BRAHIM Kamel

Reputation: 13794

that's normal since your MyClass is a reference type so you are passing a reference to original data not the data itself this why it's an expected behavior here is an explanation of what a reference type is from Parameter passing in C#

A reference type is a type which has as its value a reference to the appropriate data rather than the data itself

Upvotes: 3

Mark Fitzpatrick
Mark Fitzpatrick

Reputation: 1624

The docs at MSDN say it pretty clearly. Value types are passed as a copy by default, objects are passed as a reference by default. Methods in C#

Upvotes: 1

Related Questions