Search code examples
c#ooppass-by-referencepass-by-value

In C#, when a variable passes through a function/method, will the original variable change?


I am confused by how a function can change a variable passed through it. For example, if I created a variable t = 1, and pass a function by adding 2 to it, inside the function t is 3, but in Main function t is still 1:

static void Main(string[] args)
{
    int t = 1;
    addTwo(t);
    Console.WriteLine(t); 
    ## t=1
    Console.ReadLine();
}
static void addTwo(int t)
{
    t+=2;
    Console.WriteLine("inside function {0}",t); 
    ## t=3

the value of t inside the function is 3, but in Main the value stays 1.

However if I created an array "happiness" with value {2,3,4,5,6} and passed through a function "SunIsShining" which will increase each value by 2. Afterwards, I thought the array happiness should still be {2,3,4,5,6}. However it became {4,5,6,7,8}.

static void Main(string[] args)
{
    int[] happiness = { 2, 3, 4, 5, 6 };
    SunIsShining(happiness);
    ## happiness = { 4, 5, 6, 7, 8 }

    foreach (int y in happiness)
    {
        Console.WriteLine(y);
    }
    Console.ReadLine();
}
static void SunIsShining(int[] x)
{
    for (int i = 0; i < x.Length; i++)
        x[i] += 2;
}

Could anyone help me to understand the reason? Thank you!


Solution

  • The difference in semantics between your two examples is not explained by the different types, but rather by you doing different things to the parameter in your two examples.

    In the first example, you are assigning to the parameter (t += 2 is equivalent to t = t + 2. t = something is assignment to t.) In the second example, you do not assign to x; rather, you are using the array element syntax to modify the internal contents of the array pointed to by x. If you did the same thing you did in your first example in your second example, i.e. assign directly to the parameter x, you would see that the called scope is similarly not affected by the assignment:

    static void Main(string[] args) {
        int[] happiness = { 2, 3, 4, 5, 6 };
        SunIsShining(happiness);
    
        foreach (int y in happiness) {
            Console.WriteLine(y); // prints 2, 3, 4, 5, 6
        }
    }
    static void SunIsShining(int[] x) {
        x = new int[] {7, 8, 9};
    }
    

    So despite your first example passing int, a value type, and this example passing int[], a reference type, you can see that the result of assigning to the parameter in the called function is the same in both cases. And in fact this is true for all other types too -- if the method parameter is not marked ref or out, then no matter what type the parameter is, direct assignment to that parameter variable in the called function will never have any effect on a passed variable in the calling function.

    Passing a variable to a method parameter that is not ref or out is exactly like assigning to a new variable. So rather than thinking about passing to methods, you can just think about what happens when you assign to a new variable. When you do this for your first example, you assign one int to another. Since int is a value type, each int variable holds its own integer, and assignment copies the integer so both variables' integers have the same value. Then when you assign to the second variable, you change that integer, but it doesn't affect the first variable's integer. This is pretty easy to understand.

    int tInMain = 1;
    
    int tInAddTwo = tInMain;
    tInAddTwo += 2;
    Console.WriteLine("inside function {0}", tInAddTwo); // prints 3
    
    Console.WriteLine(tInMain); // prints 1
    

    If you do this with my example above, you see we assign one int[] variable to another. int[] is a reference type, so the value of such a variable is a "reference" (basically, in C++ parlance, a pointer to an object). When you copy one reference to another, you get two references that have the same value, i.e. they both point to the same object. When you then assign a reference to another object to one of the variables, you just make that variable point to that other object. It does not do anything to the object that it used to point to (which the first variable still points to).

    int[] happiness = { 2, 3, 4, 5, 6 };
    
    int[] x = happiness;
    x = new int[] {7, 8, 9};
    
    foreach (int y in happiness) {
        Console.WriteLine(y); // prints 2, 3, 4, 5, 6
    }
    

    And if you do this with your second example, you see that you again assign one reference to another, so you have two reference variables pointing to the same object. But then you use one of those references to make a change to the contents of the object that is pointed to. This change is then obviously visible when you look at the object pointed to by the other reference, since it's the same object that is being pointed to.

    int[] happiness = { 2, 3, 4, 5, 6 };
    
    int[] x = happiness;
    for (int i = 0; i < x.Length; i++)
        x[i] += 2;
    
    foreach (int y in happiness)
    {
        Console.WriteLine(y); // prints 4, 5, 6, 7, 8
    }
    

    Now you might ask why you can't do something like this with your first example. The reason is that the operation you are doing, i.e. "modify the object pointed to by the reference", simply doesn't exist for the type int. Most obviously, this is because int is a value type. But even for reference types, you might not be able to do this, because the object that is being pointed to might not offer you a way to modify its contents. For example, consider the following example where we box the int into an object variable of type object:

    static void Main(string[] args) {
        object t = 1;
        addTwo(t);
        Console.WriteLine(t); // prints 1
    }
    static void addTwo(object t) { // <-- object is a reference type
        // there is nothing you can do to t here
        // that will change what is printed in Main
    
        // as described above, assignment to t
        // will never affect the calling scope:
        t = (int)t + 2;
    }
    

    This is similar to your first example, except that the parameter type is object, a reference type (the type of references to objects). But this object that the int is boxed into simply does not offer a way for you to modify its contents. So even though in this case, you passed a reference, and the t in addTwo and the t in Main point to a single object, it doesn't really matter since you can't make a change to that object that can be seen through the other reference.