Domanda

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?

È stato utile?

Soluzione

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++.

Altri suggerimenti

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.

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.

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

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.

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.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top