The ref keyword in C# with Reference and Value types

Photo by Jack Church on Unsplash

The ref keyword in C# with Reference and Value types

The ref keyword in C# can be used when passing a variable that holds a Value or a Reference type to a function.

In this article I want to show you how this keyword works and how you can take advantage of it to avoid a possible bug.

Ref and Value types

When you have a struct or an Integer that's passed by ref to a function, changing the variable value in the function will change the original variable value that was passed to the function.

// assuming we have struct Book
void TestRef(ref Book b) {
   b.Name = "New Name";
}

var book = new Book() {
   Name = "Old Name"
};

Console.WriteLine(book.Name);  // Output: Old Name
TestRef(ref book);
Console.WriteLine(book.Name); // Output: New Name

The results we get here makes sense. We are passing a Value type and because we used the ref keyword, we are going to be changing the original book's Name property.

Here is what is happening:

  1. Calling TestRef(ref book) causes the TestRef function to create a local variable 'b', and assigns an address of the book object to it.
  2. When TestRef makes a change to the Name property of this local 'b' variable, it's actually changing the value of the book variable's property 'Name'. This is because b is acting like a pointer to the variable book.

Now let's look at what happens when you pass in a reference type with the ref keyword!

Ref and Reference types

To explain reference types, let's first look at how reference types work without the 'ref' keyword:

// assuming we have class Book - a reference type
void TestRef(Book b) {
   b.Name = "New Name";
}

var book = new Book() {
   Name = "Old Name"
};

Console.WriteLine(book.Name);  // Output: Old Name
TestRef(book);
Console.WriteLine(book.Name); // Output: New Name

Did you notice that we get the same behaviour as a Value type with the 'ref' keyword?

image.png Passing by Reference without the ref keyword, or a Value type with a ref keyword

As you can see, the local variable 'b' in the function points to the variable on the stack. This in turn points to the heap where the actual Book object lives. When we make a change to the Name property on b, we are actually changing the value on the Heap.

What happens if you create a new Book inside TestRef and assign it to the variable b?

// assuming we have class Book - a reference type
void TestRef(Book b) {
   b.Name = "New Name";
   b = new Book() {
      Name = "Changed"
   };
}

var book = new Book() {
   Name = "Old Name"
};

Console.WriteLine(book.Name);  // Output: Old Name
TestRef(book);
Console.WriteLine(book.Name); // Output: Changed <- this changed

Our function has the power to mess up with the book variable's value! This could be a source of a bug if we are not careful.

image.png Passing by reference without ref keyword - gotcha!

So, now we have a question to ask - how do we stop reference types from being reassigned in the TestRef function? If you guessed it, it is by using the ref keyword.

Here is the same code, but this time we are using the ref keyword with our reference type.

// assuming we have class Book - a reference type
void TestRef(ref Book b) {
   b.Name = "New Name";
   // Code below does not change original book address
   b = new Book() {
      Name = "Changed"
   };
}

var book = new Book() {
   Name = "Old Name"
};

Console.WriteLine(book.Name);  // Output: Old Name
TestRef(ref book);
Console.WriteLine(book.Name); // Output: New Name

When we use ref on a reference type, the 'b' variable on TestRef points to the actual item on the heap. In other words, we now have 2 references to the heap.

Here is 'Before creating a new instance of Book in the 'TestRef' function:

image.png

And here is 'After creating a new instance of Book in the TestRef' function:

image.png

In short, using ref with reference types stops us from re-assigning a new object to the caller variable passed to our function.

Note that ref cannot be used with asynchronous methods!