Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What does the ref keyword mean before the return type in C#

Tags:

c#

In the code below, what is the significance of the ref in the GetAge() method signature?

public class Person
{
    private int age;
    public ref int GetAge()
    {
        return ref this.age;
    }
}
like image 659
CodingYoshi Avatar asked Feb 04 '23 15:02

CodingYoshi


1 Answers

The ref return is a new feature in C# 7.0. It allows to return a reference to a memory position. This was not possible in previous C# versions. You can even store the returned memory location like this:

var person = new Person();

// Here we can store the reference to the memory area and we can modify it
ref int age = ref person.GetAge();

// like this
age = 50;

The whole time we were working on the same memory location and not a copy of the age.


What is happening behind the scenes?

If we have this code:

public class Program
{
    public static void Main()
    {
        var person = new Person();

        // Here we can store the reference to the memory area and we can modify it
        ref int age = ref person.GetAge();

        // like this
        age = 50;
    }
}

public class Person
{
    private int age;
    public ref int GetAge()
    {
        return ref this.age;
    }
}

Here is what the compiler (Roslyn) does behind the scenes for that code:

using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security;
using System.Security.Permissions;
[assembly: AssemblyVersion("0.0.0.0")]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[module: UnverifiableCode]
public class Program
{
    public unsafe static void Main()
    {
        Person person = new Person();
        int* age = person.GetAge();
        *age = 50;
    }
}
public class Person
{
    private int age;
    public unsafe int* GetAge()
    {
        return ref this.age;
    }
}

OK!!! I think we are all glad we do not have to deal with all those * shenanigans.


When is this feature useful?

The addition of ref locals and ref returns enable algorithms that are more efficient by avoiding copying values, or performing dereferencing operations multiple times.

It is most useful when you are working with large data structures which are value types (struct) and passing the copies in and out of methods may not be very effective. For example, imagine we have a class which contains a bunch of struct objects:

class Container
{
    private Tile[] tiles = new Tile[] { new Tile { X = 10 } };

    public Tile this[int x]
    {
        get { return tiles[x]; }
        set { tiles[x] = value; }
    }
}

public struct Tile
{
    public int X { get; set; }
    // Many more propeties
}

If we wanted to work with the Tile objects, since they are struct, we would not be able to do this:

var container = new Container();
container[0].X = 10;

We cannot do that because the compiler will issue this error:

Error CS1612 Cannot modify the return value of 'Container.this[int]' because it is not a variable

The compiler is throwing that error to be explicitly clear that what you think you are doing (modifying the indexed item), is not really what you are doing. You are actually modifying the copy so it forces you to do exactly that. So, to be able to set the X, you will need to do it on the copy like this:

var container = new Container();
var copy = container[0];
copy.X = 10;

// now we need to set the item to the copy
container[0] = copy;

As you can see that is not very efficient, especially if we are working with a large struct and we need to manipulate many of them in an iterative way.

With C# 7.0, we can do this:

public ref Tile this[int x]
{
    get { return ref tiles[x]; }
}

and now we can manipulate the Tiles directly without having to send the copy, making the copy, and then setting the original item to the copy. Like this:

var container = new Container();
ref Tile tile = ref container[0];  
tile.X = 10;  

A Small Gotcha

There are many examples online and they have the syntax like this:

// Notice the ref missing on the right side
ref int age = person.GetAge();

That will result in this error:

Cannot initialize a by-reference variable with a value

The correct syntax is to have the ref on both sides like this:

ref int age = ref person.GetAge();

More Info

Here is an SO question wherein this feature has been discussed. I guess that question is now history. And here is another article by Eric Lippert about this feature.

like image 80
CodingYoshi Avatar answered Feb 24 '23 06:02

CodingYoshi