Location>code7788 >text

C#|.net core Fundamentals - Value Passing vs Reference Passing

Popularity:814 ℃/2024-09-19 11:16:54

I don't know if you have ever encountered such confusion during the development process: how is the value of this variable changed? How come the value hasn't changed?

Today I'm going to share with you the root cause of this problem, which may be value passing vs. reference passing.

Before that we review two sets of basic concepts:

Value Types vs Reference Types

Value Type: Direct storage of data, which is stored on the stack;

Reference Type: Stores references to data objects and the data is actually stored on the heap.

Formal vs Real

Shape Sen: That is, formal parameters, which indicate the values that the method requires you to pass when you call it. A method declaration defines its formal parameters. That is, when defining a method, the list of parameters in parentheses immediately following the method name are the formal parameters.

Real reference: I.e., the actual parameter, represents the value you pass to the method's formal parameter when you call the method. The calling code provides the actual parameters during the call. That is, the list of parameters in parentheses immediately following the method name when you call the method is the actual parameters.

Recall again how value and reference types are stored in memory?

For the value type variables are stored directly on the stack, as shown in the following figure, int a = 10, 10 is stored directly on the stack space, and the corresponding memory address of the stack space is 0x66666668; for the reference type variable itself is stored in the instance of the reference object, that is, the instance object in the heap of the actual memory address, so the reference type variable is stored in the instance of the reference object in the stack, as shown in the following figure The variable Test a in the stack actually stores the memory address of the instance object Test a in the heap 0x88888880, while the corresponding memory address in the stack space is 0x66666668.

It is important to note that the stack also has a memory address; whether a value or a reference address is stored on the stack space, this stack space itself has its own corresponding memory address.

What is value passing? What is reference passing?

value transmission: If a variable is passed to a method by value, a copy of the variable is passed to the method. For value types then theCopies of variablesis passed to the method, and for reference types it passes theA copy of the reference to the variablepassed to the method. Thus the called method parameter creates a new memory address to receive the stored variable, so modifications to the variable within the method do not affect the original value.

pass by reference: If the variable is passed to the method by reference, the reference to the variable is passed to the method, and for value types theStack space address of the variableis passed to the method, and for reference types it passes theStack space address of the reference to the variablepassed to the method. Therefore the called method parameter does not create a new memory address to receive the stored variable, meaning that the formal and real parameters point to the same memory address together, so modifications to the variable within the method affect the original value.

The above description may be a bit tongue-in-cheek, here we are based on the value type, reference type, value passing, reference passing various combinations for a detailed description.

01, value types are passed by value

When value types are passed by value, the caller passes a copy of the value type variable to the method, so the called method parameter creates a new memory address for receiving the stored variable, so when changes are made to the parameter within the method they do not affect the value type variable at the caller's call.

Passing a copy of a value variable is the equivalent of making a copy of the same value on the stack, but at a different memory address, so it doesn't affect each other. The following figure assigns a value to b, then b directly opens up a new stack space, although a and b are both 10, but they are in different address spaces, so if they are modified, they do not affect each other.

Here we write an example to demonstrate, this example is to define a variable a and assign a value, and then call a method within this method to pass in the parameter a plus 1, the specific code is as follows:

public static void ValueByValueRun()
{
    var a = 10; ($"Caller - call method before a value: {a}"); $"Caller - call method before a value: {a}")
    ($"Caller - value of a before calling the method: {a}");
    ChangeValueByValue(a);
    ($"Caller-after-call-method a value: {a}");
}
public static void ChangeValueByValue(int a)
{
    ($" called-method-received a value: {a}"); } public static void ChangeValueByValue(int a) {
    
    ($" Called method-modified a value: {a}");
}

The results of the run are as follows:

The result of the code execution shows that the modification of the variable within the method has taken effect, but not without affecting the value of the variable at the caller's invocation.

02The reference type is passed by value.

When a reference type is passed by value, the caller passes a reference copy of the reference type variable to the method, so the called method parameter creates a new memory address to receive the stored variable, and for a reference type variable itself is stored as a reference copy of the instance object of the reference type, and the method receives a copy of the reference to this variable, so the caller parameter and the called method parameter are two reference copies of the same instance object. As shown in the following figure, Test a can be interpreted as a real parameter passed by the caller, and Test b can be interpreted as a formal parameter defined by the called method, and both parameters just point to the reference copy of Test a in the heap.

Two conclusions can therefore be drawn:

1、Variables a and b are references to the instance object Test a, so regardless of variables a or b, as long as one of them updates the instance member, the other variable will also be changed simultaneously.

2, although the variables a and b are pointing to the instance object Test a reference, but they are stored on the stack memory address is different, so if they are various reallocation of the instance that is, new a new object, the other variable can not be perceived or to maintain the cause of the state is unchanged.

Let's start by illustrating the first conclusion in code:

public static void ChangeReferenceByValueRun()
{
    var a = new Test
    {
        Age = 10
    }; var a = new Test { Age = 10
    ($"caller-before-calling-method Value: {}");
    ChangeReferenceByValue(a);
    ($"caller-after-call-method Value: {}");
}
public static void ChangeReferenceByValue(Test a)
{
    ($" called-method-received Value: {}"); }
     = + 1;
    ($" called method-modified Value: {}");
}

The results of the run are as follows:

You can see that when the Age attribute of the a instance object in the called method changed, the variable in the caller also changed synchronously.

For the second conclusion we argue this way, in the method directly to the parameter new a new object to see if the original variable has changed, the code is as follows:

public static void NewReferenceByValueRun()
The following is an example of a new Test.
    var a = new Test
    {
        Age = 10
    }; var a = new Test { Age = 10
    ($"caller-call method before Value: {}");
    NewReferenceByValue(a);
    ($"caller-after-call-method Value: {}");
}
public static void NewReferenceByValue(Test a)
{
    ($" called-method-received Value: {}"); } public static void NewReferenceByValue(Test a) {
    a = new Test
    {
        Age = 100
    }; a = new Test { Age = 100
    ($" Called method-new after Value: {}");
}

The results of the implementation are as follows:

It can be noticed that when a new operation is performed on a variable in a method, the variable at the caller does not change.

Why is this so? Because for the reference type, the formal parameter and the real parameter is the reference type of the instance object reference of the two copies, and these two copies are stored on the stack and respectively in different memory address space, and new is the main reallocation of memory, which leads to the formal parameter variable a = new after the stack formal parameter variable a points to the Test new instance object reference, while the real parameter variable a is still to keep the original instance object reference remains unchanged.

As shown in the figure below.

03, value types are passed by reference

When value types are passed by reference, the caller passes the address of the stack space corresponding to the value type variable to the method, so the called method parameter does not create a new memory address to receive the stored variable, so when the parameter is modified within the method and also affects the value type variable at the caller's call.

Passing the address of the stack space corresponding to a variable of a value type means that the formal parameter points to the same memory address as the real parameter, which is why changes to the formal parameter are synchronized with changes to the real parameter.

Let's demonstrate with a small example:

public static void ValueByReferenceRun()
{
    ($"Value type is passed by reference");
    var a = 10;
    ($"Value of a before caller-call method: {a}");
    ChangeValueByReference(ref a);
    ($"caller-after-call-method a value: {a}");;
}
public static void ChangeValueByReference(ref int a)
{
    ($" called method-received a value: {a}"); }
    a = a + 1; ($" called method-received a value: {a}");
    ($" called method-modified a value: {a}");
}

The results of the implementation are as follows:

It can be noticed that the value type variable at the caller has changed.

04, reference types are passed by reference

When a reference type is passed by reference, the caller passes the address of the stack space corresponding to the reference type variable to the method, so the called method argument does not create a new memory address to receive the stored variable, so when the argument is modified within the method and also affects the reference type variable at the caller's invocation.

Passing the address of the stack space corresponding to the reference type variable means that the formal parameter points to the same memory address as the real parameter, so when the formal parameter is modified, the real parameter will be changed synchronously, and the modification does not only refer to the modification of the instance members, but also includes the creation of a new instance object.

Let's look at an example of modifying an instance member:

public static void ChangeReferenceByReferenceRun()
{
    ($"Reference Type Passing by Reference - Change Instance Member");
    var a = new Test
    {
        Age = 10
    }; var a = new Test { Age = 10
    
    ChangeReferenceByReference(ref a);
    ($"caller-after-call-method Value: {}");
}
public static void ChangeReferenceByReference(ref Test a)
{
    ($" called-method-received Value: {}"); }
     = + 1; ($" Called method-received Value: {}");
    ($" called method-modified Value: {}");
}

The results of the implementation are as follows:

Look at the example of NEW a new object again:

public static void NewReferenceByReferenceRun()
{
    ($"Reference type passed by reference - new new instance");
    var a = new Test
    {
        Age = 10
    }; var a = new Test { Age = 10
    ($"Caller - before calling method Value: {}"); var a = new Test { Age = 10 }; var a = new Test { Age = 10 }
    NewReferenceByReference(ref a);
    ($"caller-after-call-method Value: {}");
}
public static void NewReferenceByReference(ref Test a)
{
    ($" called-method-received Value: {}"); } public static void NewReferenceByReference(ref Test a) {
    a = new Test
    {
        Age = 100
    }; a = new Test { Age = 100
    ($" Called method-new after Value: {}");
}

The results of the implementation are as follows:

In addition, string is a special reference type, string type variable pass by value and pass by reference and value type is the same, that is, the string type as a value type on the same as the value type. string type of special features we will be introduced later separate specific.

In C#, the following modifiers can be applied to parameter declarations and will cause the parameter to be passed by reference: ref, out, readonly ref, in. I won't go into the details of how each of these modifiers is used here.

I believe that by this point you should be able to answer the question I asked earlier at the end of LeetCode Problem Set - 2 - Adding Two Numbers.

classifier for sums of money: The test method code as well as the sample source code have been uploaded to the code repository for those who are interested./hugogoos/Planner