Reputation: 780
I understand that c# ref allows you to send a pointer to an object. I'm just wondering what this means for what is actually being sent to a function under the hood.
As far as I understand it is like:
DoSomething (ref exampleObject) --> here a pointer to exampleObject is sent to the function
DoSomething (exampleObject) --> here an array of pointers to the object's properties is sent to the function.
Is this correct? And will the second function also create a pointer for value types? (Seeing as they are properties and their value has to be saved on the heap in order to retain the referential integrity of the object)
A bit clearer explanation: *Object A is in memory location 5 *It has the properties object B and object C
Now I call DoSomething (A) --> as objects are handled via pointers by default I pass a pointer value of 5, so that I may manipulate the value at memory adress 5. How does C# know at which memory adress the properties of the sent object are located?
Now I call DoSomething (ref A) --> At this point I create a new pointer (let's say 6) which points to the other pointer. So now I pass in 6, which allows me to change the value of memory adress 6, which conveniently turns out to be 5, the location of object A. So now I can change the memory to which the A object passed in is pointing to.
The thing I do not understand is how the properties are found in memory? Somewhere an array of pointers or a pointer to an array of pointers has to be send, right?
Upvotes: 2
Views: 386
Reputation: 26486
I always compare pointers to voodoo dolls - whatever you do to the voodoo doll, will affect the person the voodoo doll "points" to.
so C# reference is like a voodoo doll - it attaches to an object, and you can affect the object by consuming the voodoo doll.
but what happens if I suddenly want the voodoo doll to point to different person than it pointed before? using the voodoo doll affects the person it points to - not the voodoo doll itself.
what is the solution? creating another voodoo doll - to the original voodoo doll! this way I can mess with the original doll and not only with the person it points to.
so ref
in C# is a reference to a reference - it allows you to modify a reference, and not only the object it points to.
behind the scenes, a reference is just number which indicates where the object sits on the RAM. a ref
reference is yet another number, but which indicates where is the original reference sits on the RAM, and not where does the object it points at sits.
Upvotes: 0
Reputation: 3718
No, this is not correct.
What ref
brings to the table is reference semantics.
When you call a function foo(T arg)
- regardless of whether T is a value type or a reference type (I can't stress that enough) - foo
gets a copy of T.
The difference between value types and reference types is that names of value types refer to the interesting stuff, while names of reference types refer to references (or pointers) to the interesting stuff. It follows that when you copy an object of a value type you get a copy of the interesting stuff and when you copy of said reference to the interesting stuff.
Some image I found on Google instead of drawing one on my own:
The copies in the image are basically what happens when you call the foo(T arg
) is was talking about. In both cases we arg
contains a copy of what was passed to foo
. If T
is int
it's the number you're interested in. If T
is Circle
then arg
is the "address"1 that the argument passed to foo
contained. Instead of talking about pointers or references we can talk about arrows. variables of reference types contain arrows to the interesting stuff, while variables of value types contain the stuff itself.
This is demonstrated by the following code:
using System;
class MainClass {
public static void Main (string[] args) {
Foo foo1 = new Foo();
foo1.i = 1;
Console.WriteLine("Main(): foo1.i: {0}", foo1.i);
doStuffToFoo(foo1, 5);
Console.WriteLine("Main(): foo1.i: {0}", foo1.i);
Console.WriteLine();
Bar bar1;
bar1.j = 1;
Console.WriteLine("Main(): bar1.j: {0}", bar1.j);
doStuffToBar(bar1, 5);
Console.WriteLine("Main(): bar1.j: {0}", bar1.j);
}
static void doStuffToFoo(Foo aFoo, int aInt) {
aFoo.i = aInt;
aFoo = new Foo();
aFoo.i = 123;
Console.WriteLine("doStuffToFoo: aFoo.i: {0}", aFoo.i);
}
static void doStuffToBar(Bar aBar, int aInt) {
aBar.j = aInt;
aBar = new Bar();
aBar.j = 123;
Console.WriteLine("doStuffToBar: aBar.j: {0}", aBar.j);
}
}
class Foo {
public int i;
}
struct Bar {
public int j;
}
See if you understand what it prints before trying or reading on.
The variable aFoo
inside doStuffToFoo
is a copy of foo1
. Plain and simple. aFoo
and foo1
are distinct variables (stored in distinct locations on the stack - foo1
in Main
's stack frame and aFoo
in doStuffToFoo
's stack frame - assuming there is a stack, since the stack is an implementation detail), and they both contain arrows to the same Foo
object.
When you refer to fields of both these variables they access the same Foo
object through their respective arrows. But when you assign a new Foo()
to aFoo
it only changes the arrow in aFoo
to point to the new Foo
object. It does nothing to the other arrow to the first Foo
object, stored safely inside the foo1
variable and inaccessible to the code in doStuffToFoo
. That's why you don't see "123" back in Main
.
And all that brings us to ref
. ref T v
says that v
is not a new variable of type T (that contains some interesting stuff if T
is a value type or an arrow to some interesting stuff if T
is a reference type). Rather v
is an alias to whatever gets passed as argument. It is a second name to some other variable.
If you add the following code to the example above you'll see that in effect.
/***** inside Main: *****/
Foo foo2 = new Foo();
Foo foo3 = foo2;
foo2.i = 1;
Console.WriteLine("Main(): foo2.i: {0}", foo2.i);
Console.WriteLine("Main(): foo3.i: {0}", foo3.i);
doStuffToFooRef(ref foo2, 5);
Console.WriteLine("Main(): foo2.i: {0}", foo2.i);
Console.WriteLine("Main(): foo3.i: {0}", foo3.i);
/***** until here *****/
static void doStuffToFooRef(ref Foo aFoo, int aInt) {
aFoo.i = aInt;
aFoo = new Foo();
aFoo.i = 123;
Console.WriteLine("doStuffToFooRef: aFoo.i: {0}", aFoo.i);
}
Now aFoo
is a another name to foo2
. When you assign to "aFoo
" you assign to "foo2
", because both these names refer to the same variable (storage location). That's what happens during the assignment in doStuffToFooRef
:
(i
is still zero since it's before we put there 123.)
Try it here: https://repl.it/E80a
C# ref
is like C++ references in this respect, as Александр Лысенко wrote in his answer. While technically, a ref SomeReferenceType x
may be "pointer to pointer" as some have said, this doesn't catch the real meaning of ref
, and dwells on an implementation detail that needn't be always true (it might be optimized out). It implies that adding ref
adds a pointer. So is a ref-of-a-ref of a reference type is a pointer to a pointer to a pointer? Does each additional ref
adds a level of indirection?
1 For the purposes of this discussion we don't care if it's really an address or something else. Since objects may be moved in memory, we understand that it's probably not a virtual memory address of the host machine/operating system, but it is an address in the broad sense.
Some make a distinction between references and pointers, the latter are numerical memory addresses, but others aren't afraid to call these things pointers too, because they point to objects. For example, the Java Language Specification (§4.3.1) explicitly says:
The reference values (often just references) are pointers to these objects, and a special null reference, which refers to no object.
They emphasize "references" - the new term they introduce - but what pointers are is obvious to the reader in their opinion, and there too objects are not unmovable AFAIK.
Upvotes: 0
Reputation: 24147
We can use DoSomething(ref exampleObject)
if we know or expect that DoSomething
may perform the act of "calculating a value or creating a new object and returning it to us". And that we know if the declaration of DoSomething
also contains the ref
keyword.
If before the call your exampleObject
contains a certain value, or it is pointing to a certain object (or even null), then after the call it may contain a new value, or it may be pointing to a new object or it may have been changed to null.
There is no certainty or obligation for this to happen with ref
, just that if DoSomething
decides that exampleObject
should change, then we will get that new value or object (or null). And otherwise we keep what we had before.
This is true for both value types (numbers, bools, structs) and reference types (objects), that is why I kept saying "a new value or a new object or null".
Edit:
I deliberately avoided to mention pointers. Unless you are doing low level stuff, in C#/.NET you almost never have to know about pointers. It usually suffices to understand the concepts and the rules that govern them.
Upvotes: 0
Reputation: 4512
No. If you came in c# from c++ you can imagine ref as reference in c++ like that:
void foo(A*& a)
{
// Here you can change object's state
// Also you can change value of pointer
// Caller will have new value of pointer
}
void foo(A* a)
{
// Here you can change object's state
// Also you can change value of copy of pointer
// Caller will have old value of pointer
}
Upvotes: 1
Reputation: 17605
To my understanding in the second case
DoSomething(exampleObject)
the address of exampleObject
is given as an argument to the method DoSomething
, which means that in DoSomething
the object's properties can be reassigned (supposing a reference type is used), but assignmet of exampleObject
does not change it in the scope of the caller, as the address of exampleObject
is modified on the stack, not in the scope of the caller. On the other hand, in the first case
DoSomething(ref exampleObject)
a pointer to the address of exampleObject
is given as an argument to DoSomething
, which consequently means that besides changing the properties of exampleObject
, the reference to exampleObject
itself can be reassigned. Consequently,exampleObject
can be exchanged, an effect which is also visible within the scope of the caller.
Upvotes: 1