Before discussing the topic of this article, we will first introduce the value type and reference type in C #.
We all know that C # has value and reference types. value types include basic data types (such as int, double, bool, etc.), struct, and enumeration. reference types include interfaces, classes, and delegation.
All value types are applied in the stack space of the operating system, and the reference type creates an object in the heap space of the operating system, and then applies for a pointer to the address of this object in the stack space.
Therefore, the reference type of C # is actually like the pointer type of C ++.
Next, let's take a look at the function parameter passing problem.
As early as the C era, the concept of passing value and passing address of function parameters was introduced. Remember that in C #, function parameters are passed by default.
- For the value type, the function copies the value of the real variable in the stack space and passes it to the variable. Therefore, changing the parameters in a function will not affect the real parameters, because the function parameters are only copies of the real parameters.
- For the reference type, because both the real and shape parameters are reference types, they all point to the address of an object in the memory heap, the function copies the address value pointed to by the real parameter variable to the same address in the heap, so the changes made to the object pointed to by using the parameter variables in the function will also be reflected in the real variable.
Therefore, when both the value type and the reference type are passed into the function as parameters, they are actually passed in values, but the reference type is passed through the address of the object in the heap.
In addition, from the above definition, we can see that the variables of the reference type in C # are equivalent to pointers of the reference type in C ++. For example, there is a class (reference type) RefClass:
RefClass rc is equivalent to the RefClass * rc on C ++.
When rc. IntValue ++; is used in C #, it is equivalent to rc-> IntValue ++ of C ++;
Because the reference type is used to transmit the address when the function transmits parameters, an inertial thinking is formed in my head. I think that as long as the reference type is passed into the function, any changes made in the function will be reflected in real parameters. However, I found that this is not exactly the case. The following is an example (the comments are equivalent to the C ++ code ):
Using System;
Using System. Collections. Generic;
Using System. Linq;
Using System. Text;
Namespace RefWarn
{
Class RefClass
{
Public int IntValue
{
Get;
Set;
}
}
Class Program
{
Static void AddValue (RefClass prc) // RefClass * prc, prc and the passed rc point to the address of the same RefClass object
{
Prc. IntValue ++; // prc-> IntValue ++;
}
Static void AddValue (ref RefClass prc) // RefClass ** prc. prc points to the address of the passed rc.
{
Prc. IntValue ++; // (* prc)-> IntValue ++;
}
Static void ChangeRef (RefClass prc) // RefClass * prc, prc and the passed rc point to the address of the same RefClass object
{
Prc = new RefClass () {IntValue = 1000 };
// Prc = new RefClass () {IntValue = 1000}; note that the new keyword in C ++ creates a pointer to an object instead of an object.
}
Static void ChangeRef (ref RefClass prc) // RefClass ** prc. prc points to the address of the passed rc.
{
Prc = new RefClass () {IntValue = 1000 };
// * Prc = new RefClass () {IntValue = 1000}; note that the new keyword in C ++ creates a pointer to an object instead of an object.
}
Static void Main (string [] args)
{
RefClass rc = new RefClass () {IntValue = 1}; // RefClass * rc = new RefClass () {IntValue = 1 }; note that the new keyword in C ++ creates a pointer to an object instead of an object.
AddValue (rc); // rc, passing a pointer to the RefClass object
Console. WriteLine ("after AddValue (rc) is called, IntValue is:" + rc. IntValue );
Rc. IntValue = 1;
AddValue (ref rc); // & rc, passing a pointer to the RefClass object pointer
Console. WriteLine ("after AddValue (ref rc) is called, IntValue is:" + rc. IntValue );
Rc. IntValue = 1;
ChangeRef (rc); // rc, passing a pointer to the RefClass object
Console. WriteLine ("after ChangeRef (rc) is called, IntValue is:" + rc. IntValue );
Rc. IntValue = 1;
ChangeRef (ref rc); // & rc, passing a pointer to the RefClass object pointer
Console. WriteLine ("after ChangeRef (ref rc) is called, IntValue is:" + rc. IntValue );
}
}
}
After ChangeRef (rc) is called in the Main function, rc does not change. The IntValue of its attribute is 1.
Why? Let's take a look at the structure of the static void ChangeRef (RefClass prc) function to see what has been done in it.
Static void ChangeRef (RefClass prc) // RefClass * prc, prc and the passed rc point to the address of the same RefClass object
{
Prc = new RefClass () {IntValue = 1000 };
// Prc = new RefClass () {IntValue = 1000 };
}
We can see that the prc variable of the RefClass type is assigned a value again in the function. However, we can see that this assignment does not reflect the reference variable rc. The reason is actually very simple, as mentioned at the beginning of this article. Because the real-variable pc and the rpc variable both reference type variables, they are actually two pointers in the operating system stack space, it only points to the same RefClass object in the heap space of the operating system. In the function ChangeRef, assign a value to the reference variable rpc, which means that the rpc pointer in the stack is directed to another RefClass object in the heap. After the address that rpc points to changes, the point to the Real Variable pc does not change, so the pc still points to the RefClass object before the function ChangeRef (RefClass prc) Call.
However, you may ask why the function has changed the rc after the AddValue (rc) is executed? Let's take a look at the AddValue (RefClass prc) Function
Static void AddValue (RefClass prc) // RefClass * prc, prc and the passed rc point to the address of the same RefClass object
{
Prc. IntValue ++; // prc-> IntValue ++;
}
Note that the AddValue function does not change the property of the RefClass object to which the rc points, the reason is that both the real variable pc and the Remote Variable rpc point to the same RefClass object principle. In AddValue, rpc changes the attributes of the RefClass object to which it points, that is, the property of the pc pointing to the RefClass object is changed. Therefore, after executing AddValue (rc), it seems that rpc and pc are the same variable. Changing rpc is equivalent to changing pc. But remember that this is absolutely wrong. rpc and pc are two completely different reference variables, but point to the same RefClass object in memory.
Finally, we will discuss whether there is a way for the function to make the parameter of the reference type completely equal to the real parameter when passing the parameter of the reference type? Can it be reflected to the real parameter regardless of whether the form parameter is re-assigned or changed?
The answer is definitely the ref keyword.
When this keyword is used in the value type, it is equivalent to the pointer type of C ++, for example:
Ref int param
It is equivalent to C ++'s
Int * param
And the Pointer Points to its corresponding real Variable
So when we use the int-type parameter param. ToString () declared as ref in C #, it is equivalent to using the int pointer * param. ToString () on C ++ ()
Therefore, when using the int parameter param declared as ref, it is equivalent to * param on C ++. The operation is the int variable that param points to, that is, the real parameter.
When this keyword is used before the reference type, it is equivalent to pointing to the address of the reference type variable. As mentioned earlier, the C # reference type variable is equivalent to the C ++ pointer, the address that points to the referenced type variable is also the pointer to the pointer.
As mentioned above, RefClass rc is equivalent to C ++ RefClass * rc.
Then ref RefClass rc is equivalent to C ++ RefClass ** rc
When the RefClass variable rc. ToString () declared as ref is used in C #, The RefClass pointer * rc-> ToString () is used on C ++ ()
Therefore, when using the RefClass type parameter rc declared as ref, it is equivalent to * rc on C ++ (note * rc or pointer, because rc is a pointer to the pointer ), the reference variable of the RefClass type pointed to by the rc parameter (that is, the rc points to the address of the real variable, and the non-Real Variable points to the address of the object in the heap space), that is, the real parameter.
The ref before the real parameter is equivalent to the & Symbol of C ++, that is, the address of the variable.
Therefore, when the ref parameter is added before the function parameter, the parameter variable points to the address of the real parameter variable, but if the real parameter type is a value type, the Parameter Variable points to the address of the real variable in the operating system stack. If the real parameter is of reference type, the parameter variable points to the address of the real variable in the operating system stack, but the real variable points to the address of the object in the Operating System Heap. Therefore, no matter whether it is a reference type or a value type, as long as it is added with a ref before it as a form parameter, the form parameter is a pointer to the real parameter variable, the parameter variable is equivalent to the parameter variable.
In the end, it must be clear that when the reference type is used as a function parameter, the difference between ref and no ref must be added.
Take RefClass rc for example:
- Without ref, the rc variable is a pointer to the RefClass object in the heap space of the operating system. The rc variable and the Real Variable both point to the address of the RefClass object, and there is no direct relationship between the variable and the real variable, the changes made to the RefClass object through the rc variable can also be seen through the real variable, but the rc itself is changed (such as changing the address to which it points ), it does not affect the real variable.
- When ref is added, the rc variable points to the address of the real variable, and the rc directly points to the real variable. Because the real parameter itself is a pointer, the rc variable points to the pointer, * rc is equivalent to the real variable. * Rc is used to change the parameter variable.