4

Is it normal that 'user1' and 'user2' point to the same reference in the example below?

I know that I am passing the same variable 'notUsed' to both parameters, but it's not yet instanced so it does't contain a reference to anything. I was quite shocked to see that user1 and user2 are linked to each other.

static void CheckPasswords(out string user1, out string user2)
{
    user1 = "A";
    user2 = "B";

    Console.WriteLine("user1: " + user1);
    Console.WriteLine("user2: " + user2);
}

public static void Main()
{
    string notUsed;
    CheckPasswords(out notUsed, out notUsed);
}

Console shows:

user1: B
user2: B
John
  • 1,011
  • 11
  • 18
  • 4
    What do you mean? How do you know they're linked? – mason Aug 28 '17 at 16:15
  • As you pass the same reference for user1 and user2 into that method, yes it is normal – Tobias Theel Aug 28 '17 at 16:16
  • @TobiasTheel There are no structs here. – itsme86 Aug 28 '17 at 16:16
  • 3
    You're shocked? I fail to see what you would have expected as a result. One variable `notUsed` having two values? – oerkelens Aug 28 '17 at 16:17
  • 1
    What are you expecting the output and value of `notUsed` to be and why? – Lee Aug 28 '17 at 16:17
  • Your code is : `notUsed = "A"; notused = "B"`, so when you printing you will get same results – Fabio Aug 28 '17 at 16:17
  • 1
    @mason I'm going to assume that both outputs show the same value, though OP failed to include that bit of information. – itsme86 Aug 28 '17 at 16:18
  • 2
    Of course it's "not yet instanced" -- it's an `out` variable, those are never instanced before you're calling the method. Nevertheless, it's the same variable referenced twice, *not* two local variables that are "copied out" at the end of the method. – Jeroen Mostert Aug 28 '17 at 16:18
  • @itsme86 u are right. had a brain freeze. string is a class ! :D – Tobias Theel Aug 28 '17 at 16:18
  • 1
    You're passing an alias to `notUsed`, so this is the same as if you passed the same reference twice. https://stackoverflow.com/questions/4807086/does-passing-a-value-type-in-an-out-parameter-cause-the-variable-to-be-boxed – Stephen Byrne Aug 28 '17 at 16:18
  • 1
    Consider what you would have expected to happen if you had declared the parameters `ref`. Then consider that `out` is the same case, except you don't initialize the variable first. – Jeroen Mostert Aug 28 '17 at 16:19
  • 1
    Also, consider that C# is deftly hiding a level of indirection from you -- `user1` and `user2` are *not strings*, they are *references to a string location* (if you must, consider `out string` as if it was a separate type with interesting semantics, same as `ref string`). While `notUsed` holds no reference, `user1` and `user2` *do*. The assignment `user1 = "A"` is really `*user1 = "A"` (if C# used pointers that way, which it does not). – Jeroen Mostert Aug 28 '17 at 16:22
  • @Lee I don't care about the value of notUsed, as its name indicates. – John Aug 28 '17 at 16:23
  • @JeroenMostert When does `notUsed` get its reference? – John Aug 28 '17 at 16:26
  • 2
    @John: at `user1 = "A"`. Since `user1` points to `notUsed`, the reference to `"A"` ends up in `notUsed`. (And then, of course, it is overwritten by the second statement, since `user2` also points to `notUsed`.) The *storage* needed for `notUsed` already exists, even if its *contents* does not at method entry (or more accurately, the contents are indeterminate/inaccessible). – Jeroen Mostert Aug 28 '17 at 16:26
  • @JeroenMostert Thanks! You can put all of that in an answer if you want and I'll mark it as answered. – John Aug 28 '17 at 16:39
  • " it's not yet instanced so it doesn't contain a reference to anything": An `out` parameter is initially unassigned by definition so it doesn't matter if the variable you pass in is initialized or not, you can't use that value inside the function and, normally, not after the function either. (If an exception occurs inside the function before the variable is assigned, it retains the same value as before the call.) – Tom Blodget Aug 28 '17 at 16:41

3 Answers3

10

When you use the out keyword, you pass by reference. (https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/out-parameter-modifier) Since you are passing by reference, your user1 and user2 are both pointing to the same variable. Hence, when you update one, it updates the other.

Michael Sharp
  • 496
  • 3
  • 10
3

You are passing the variable in by reference. Also, the order of assignments in the method is significant - as written here the variable will contain "B" at the end. If you reverse them, then "A" will be produced.

Compare:

user1 = "A"; // notused now contains "A"
user2 = "B"; // notused now contains "B"
// method ends with the variable notused containing "B"

Versus:

user2 = "B"; // notused now contains "B"
user1 = "A"; // notused now contains "A"
// method ends with the variable notused containing "A"
Peter B
  • 22,460
  • 5
  • 32
  • 69
1

This:

CheckPasswords(out notUsed, out notUsed);

does not pass the contents of notUsed to the method (as would happen in a method that didn't use out parameters), it passes a reference to notUsed to the method. The same reference twice, in fact. As you say, at this point notUsed does not contain a reference itself yet, but that doesn't matter -- we are not doing anything with the contents, in fact, we don't care since we're passing it as out. Then this:

user1 = "A";

does something special because user1 is not a string parameter -- it's an out string parameter. Rather than assign a value to some local user1, it assigns a value to what user1 is pointing to -- in this case, notUsed. At this point, notUsed holds a reference to "A". And then this:

user2 = "B";

does the same thing, but through the other parameter -- it assigns a reference to "B" to notUsed. And then these two lines:

Console.WriteLine("user1: " + user1);
Console.WriteLine("user2: " + user2);

retrieve not the contents of any local variable, but the value in notUsed, since both user1 and user2 point to it. So, of course, you'll get "B" twice.

It is no more shocking than this code:

class User {
    public string Name { get; set; }
}

void NotMagic(User user1, User user2) {
    user1.Name = "A";
    user2.Name = "B";

    Console.WriteLine("user1.Name = " + user1.Name);
    Console.WriteLine("user2.Name = " + user2.Name);
}

void Main() {
    User user = new User();
    NotMagic(user, user);
}

You probably wouldn't be surprised if this printed B twice. That there are two different parameters in NotMagic doesn't mean they can't both point to the same thing. Same with out and ref parameters, except that the syntax will hide the extra indirection for you.

Jeroen Mostert
  • 27,176
  • 2
  • 52
  • 85