13

As we all know, C# classes object are treated as references, so what happens when you pass a reference object as a reference to a method? Say we have:

public class A { ... }

and then:

public void F(ref A a) { ... }

Does the compiler find out that a is already a reference type and keep it that way, or he creates a new reference to that object?

And what if we have something like this:

public void F(ref A a)
{
    F(ref a);
}

In this code, besides the obvious StackOverflowException, does the compiler creates reference to reference ... to reference to a which is a reference object?

mipe34
  • 5,596
  • 3
  • 26
  • 38
educampver
  • 2,967
  • 2
  • 23
  • 34
  • Also see for example [Why use ref keyword when passing an Object?](http://stackoverflow.com/questions/186891/) and the threads in the "Linked" section of the column to the right, at that page. – Jeppe Stig Nielsen Mar 10 '13 at 22:20

6 Answers6

29

This is best illustrated with an example:

public class C { public int P { get; set; } }
public class X
{
    static void M(C c1, C c2, ref C c3, ref C c4)
    {
      c1.P = 11;
      c2 = new C() { P = 12 };
      c3.P = 13;
      c4 = new C() { P = 14 };
    }
    static void Main()
    {
      C q1 = new C() { P = 1 };
      C q2 = new C() { P = 2 };
      C q3 = new C() { P = 3 };
      C q4 = new C() { P = 4 };
      M(q1, q2, ref q3, ref q4);
      Console.WriteLine(q1.P);
      Console.WriteLine(q2.P);
      Console.WriteLine(q3.P);
      Console.WriteLine(q4.P);
    }
}

What happens?

q1 and c1 refer to the same object but are different variables. Mutating c1.P mutates q1.P because both variables refer to the same object, so q1 is now 11.

q2 and c2 refer to the same object but are different variables. Mutating c2 does not mutate q2 because c2 and q2 are different variables; changing one does not change the other. q2 stays 2, and the new object is lost.

q3 and c3 are two names for the same variable, and therefore refer to the same object. When you change c3.P that changes q3.P automatically because they are two names for the same thing.

q4 and c4 are two names for the same variable, and therefore mutating q4 also mutates c4.

Does that make sense?

It is unfortunate that the keyword for "make an alias to this variable" is "ref". It would have been more clear had it been "alias".

To answer your second question: no, this does not make a chain of references. Let's make a more clear example:

...
int c1 = 123;
M(ref c1);
...
void M1(ref int q1) { M2(ref q1); }
void M2(ref int q2) { M2(ref q2); }

This says that c1 and q1 are different names for the same variable, and q1 and q2 are different names for the same variable, and therefore c1, q1 and q2 are all aliases for each other. There's never a "reference to reference to variable" in C# the way there is in C++.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • Your first method could also be called like `M(q1, q2, ref qSame, ref qSame);` and then we see that it is significant that the line `c3.P = 13;` is before the line `c4 = new C() { P = 14 };` inside that method. – Jeppe Stig Nielsen Mar 10 '13 at 23:35
  • Thank you very much, excellent answer!!! I would have never thought of `ref` like `alias`, it's true what you say about the naming problem, the concept could be misunderstood. Thanks a lot again. – educampver Mar 11 '13 at 04:11
  • 2
    @e.campver: The thing is, in the CLR type system there are reference types, and there are "reference to variable" types, and they are logically different. A ref parameter is from the CLR type system's perspective a "reference to variable" type, but from the C# type system's perspective, it's more like an alias. – Eric Lippert Mar 11 '13 at 04:45
6

In a call like

F(ref a);   // ByRef parameter

the variable a is "used directly" by the body of method F. There's only one storage location. If the method F assigns to its parameter, that assignment will be visible to everyone who can see a, immediately. And conversely, if someone (outside F) assigns to a while method F is running, then the parameter of F will change to the new object "all of a sudden".

On the other hand, in a call like

F(a);  // normal value parameter

the variable a is first copied to a new variable, and then the new variable is used inside F. Now if the type of the parameter of F is a value type (like a struct or enum), the copy is done by value. So the entire data is copied. But if the type of the parameter is a reference type (class (including array type), interface, delegate), the copy of a involves a copy of the reference only.

To check your understanding of the value parameter case with a class type parameter, figure out what these methods do:

static void F1(List<int> list>)  // no ref modifier
{
    list.Clear();
}

static void F2(List<int> list>)  // no ref modifier
{
    list = new List<int>();
}

Here's some possibly interesting example with ref:

static void G(ref string a, ref string b)
{
    if (string.Equals(a, b))
        b += "_unique";

    // Is it now safe to say that a and b are distinct?
    // No. Anyone could have changed either a or b by now.
    // For example, a and b could "alias" public fields visisble to other threads.
}

As an example of the use of G above, consider the code var x = "initial"; G(ref x, ref x); in which case a will change together with b inside the method G.

Jeppe Stig Nielsen
  • 60,409
  • 11
  • 110
  • 181
3

ref just creates a reference to the original value. With reference types that "value" is the location of variable's memory. When you use ref the method can now change what the original variable reference. If you then do the same with a argument that is already ref the 2nd method simply has the same reference that the first method did.

Peter Ritchie
  • 35,463
  • 9
  • 80
  • 98
2

The two concepts are not the same. A method parameter can be modified by ref regardless of whether it is a value type or a reference type.

Passing a type by reference enables the called method to modify the object refered by the parameter or to change the storage location of the parameter.

static void Main()
{
    Foo item = new Foo("aaa");
    GetByReference(ref item);
    Console.WriteLine(item.Name)
}
static void ChangeByReference(ref Foo itemRef)
{
    itemRef = new Foo("bbb");
}

this will actually print "bbb", because in this case you did not changed the objects values but you changed the object itself

CaldasGSM
  • 3,032
  • 16
  • 26
2

When you pass a object as parameter of a method, you pass a new pointer that references to the original object. If you pass a object as ref parameter, you pass the same pointer that uses the caller method. An example,

public void F(ref A a, A b){
    a = new A(1);
    b.Property = 12;
    b = new B(2);
}

public void G(){
    A a = new A(0);
    A b = new A(0);
    F(a,b);
    System.Console.WriteLine(a + " - " + b);
}

The output is 1 - 12 because the pointer of object b doesn't change but the original object changes.

doterob
  • 94
  • 5
1

Simply stated, passing a variable as a ref parameter is like creating an alias for the original variable.

Reference types and reference parameters are distinct entities. In C # variables are always pass by value. That value may be a reference to another object or a stored value.

In other words, reference types are "passed by reference" because when you pass an object instance to a method the method gets a reference to the object instance.
In the case of reference parameters the reference is to the variable (hence why it makes sense to think of this an an alias). This is a different form of "passing by reference".

Going by your example:

public void F(ref A a)
{
    F(ref a);
}

Here its like we have a single object (the original parameter a) that is referenced infinite times. (Note that this isn't what actually happens). This diagram is intended to provide an idiomatic representation of what happens under the covers when dealing with reference parameters.

enter image description here

See section 1.6.6.1 of the 4.0 C# spec for more info.

P.Brian.Mackey
  • 43,228
  • 68
  • 238
  • 348
  • Nope, `a` is not an object, it's a reference to a variable containing a reference to an object. – Ben Voigt Mar 10 '13 at 23:02
  • @BenVoigt - I mean that it can be treated as a single object, not that it is a single object. Note what I said about "alias". If they want all the nitty gritty details they can see the part of the spec I referenced. – P.Brian.Mackey Mar 10 '13 at 23:09
  • Also, it makes no sense to say "variables are always pass by value". "Pass by value" implies parameter passing, i.e. an argument, not a variable. – Ben Voigt Mar 10 '13 at 23:11
  • @BenVoigt - Respectfully, I disagree. Eric Lippert has said this several times (and I believe Jon Skeet said the same thing in an email response he once sent me). Also, to me this makes a lot of sense when you consider "value parameter" as defined in the same section of the spec. – P.Brian.Mackey Mar 10 '13 at 23:14
  • Variables ARE values. They are not "pass by value". "pass by X" only makes sense when you are talking about a parameter, which gets passed. – Ben Voigt Mar 10 '13 at 23:22
  • @BenVoigt - You are talking about semantics. I'm talking about intuition. If the OP wants semantics he can see the spec. I'm offering an intuitive example which is better for someone trying to grasp a concept. – P.Brian.Mackey Mar 10 '13 at 23:26