tags:

views:

370

answers:

7

I was talking with a coworker about this yesterday and it got me thinking about .Net's pass by reference.

// C#
class Foo {}

static void Test(ref Foo foo) { ... };
static void Main()
{ 
    Foo f;
    Test(ref foo);
}

Has to be implemented with a double indirection because we're changing the value of the pointer. Because all reference types are references (pointers)

// C#
static void Test(Foo foo) { ... }
static void Test(ref Foo foo) { ... };

equates to something like

// C++
void Test(Foo *foo);
void Test(Foo **foo);

But if this is a VALUE type, we don't actually need the double indirection. So I'm curious if

// C#
static void Test(ref int bar) { ... }

becomes

// C++
void Test(int *bar);
// or
void Test(int **bar);

1/29/10 Update: Reading all the answers, I realize that I was not exactly clear enough in what I wanted and I was misleading by throwing in C++ to what was going on. What I was primarily interested in was how it was implemented in the CLR and what the JIT would do to produce assembly for it. Thanks for all the answers, I found them all to be correct from a perspective but I chose the one that was closest to the question I thought I had asked.

A: 

A reference is & not * you are confusing it with a pointer. So this would mean that it will be passed as:

void Test(Foo &bar);
Filip Ekberg
Which, at the ABI level, is the same thing: pass the address of bar.
ChrisV
It would however exclude the thoughts of double pointers.
Filip Ekberg
A: 

I'm pretty sure a value type like int would use only a single level of indirection.

ChrisV
+7  A: 

The right way to think of ref is an alias for a storage location. Thus, when you say

int x;
Foo(ref x);

and Foo is declared as

void Foo(ref int y)

you are to think of x and y as aliases for the same location in the method call above.

So, void Foo(ref int y) in C# is analogous with void Foo(int &y) in C++.

Jason
Peter Oehlert
+6  A: 

A reference is not a pointer.


Foo foo = new Foo();

This declares a "memory cell" which holds a reference to an instance of Foo. Then it initializes a new instance of Foo and stores the reference in the memory cell.

Bar(Foo x) { x = new Foo(); }

This declares method with a Foo parameter, which is essentially a local variable (like foo) which happens to be assigned automatically when the method is invoked with an argument.

The statement in the method creates a new instance of Foo and stores the reference to the instance in memory cell x. Memory cell foo remains unchanged.

Bar(foo);

This invokes Bar by copying the value stored in memory cell foo to memory cell x -- call by value.

Exactly the same happens if you write int instead of Foo, except that the value stored in the memory cells is not a reference but the actual value.


Qux(ref Foo y) { y = new Foo(); }

This declares a method with a Foo& parameter, which is essentially a local variable that contains the address of a memory cell which holds a reference to an object of type Foo.

Qux(ref x);

This invokes Qux by setting y to the address of memory cell x -- call by reference.

The statement in Qux creates a new instance of Foo and stores the reference to the object in the memory cell which is located at address y (which is the address of foo). So foo is assigned the reference to the new instance and changes.

Exactly the same happens when Foo is an int, except that the value stored in the memory cell passed by reference is not a reference to an object but the actual value.

dtb
A: 
  1. MyClass --> MyClass* (or, actually, shared_ptr<MyClass>)
  2. ref --> &

C#:

void doSomething(int myInt);
void doSomethingElse(ref int myIntRef);
void doSomething(Foo foo);
void doSomethingElse(ref Foo fooRef);

C++:

void doSomething(int myInt);
void doSomethingElse(int& myInt);
void doSomething(Foo* fooPtr);
void doSomething(Foo*& fooPtrRef);
Eduardo León
+5  A: 

In C#, when you have a method

void M(ref int f) { }

and you call it

int x = 123;
M(ref int x):

how does this work?

Logically, this means "x and f refer to the same storage location".

The way we actually implement that in the CLR is f is of type "managed reference to variable that can contain an integer". We pass the managed address of local variable x to M.

The analog of that in C++ would be a method that takes an &int -- a reference to a variable that can contain an int.

Is that clear?

Eric Lippert
A: 

You may want to check how it is implemented in C++/CLI. I found good explanation in a book: Expert C++/CLI: NET for Visual C++ programmers, page 83.

Foo^ is defined as "managed pointer to Foo", equivalent to UnmanagedFoo*.

int% and Foo^% are "managed references" and they are equivalent to int& and UnmanagedFoo*&.

liori