C# 7.0 – Ref returns and ref locals


Facebooktwittergoogle_pluslinkedinmail

Returning reference instead of value might be very useful – especially when one has to deal with big structures. C# allows passing parameters by reference, but a method was not able to return a reference. This has been changed with C# version 7.0.

Note: You can view all code examples on GitHub. My environment: Visual Studio 2017 and .NET Framework 4.6.1.

Ref return

To declare a method returning reference we need to use ref keyword in the method signature. The ref is also needed in the return statement.

    class Example1
    {
        public struct BigStruct
        {
            public int Value1, Value2, Value3;
            public double Double1, Double2;
            public long LongValue;
            public string Text;
        }

        public ref BigStruct GetFirst(BigStruct[] collection)
        {
            return ref collection[0];
        }
    }

But how to use this method? It returns a reference and what? How to consume it? Let’s try to do it the usual way and check if we really have a reference.

    class Example2 : Example1
    {
        public void ConsumeReference()
        {
            var collection = new[]
            {
                new BigStruct
                {
                    Text = "My value"
                }
            };

            BigStruct first = GetFirst(collection);

            // Modify
            first.Text = "Changed value";

            // Print
            Console.WriteLine(collection[0].Text);
        }
    }

Something wrong happened. The application returned the original value ("My value" ). This is actually expected. The GetFirst method returned a reference but we consumed it by-value. It means that we created a new BigStruct object by copying the value from the reference. This is not what we wanted to achieve…

Ref local

The above example shows that there has to be a different way to consume the returned reference. C# 7.0 introduces new variable types – ref locals. They are designed to keep the reference. Let’s try again.

    class Example3 : Example1
    {
        public void ConsumeReference()
        {
            var collection = new[]
            {
                new BigStruct
                {
                    Text = "My value"
                }
            };

            ref BigStruct first = ref GetFirst(collection);

            // Modify
            first.Text = "Changed value";

            // Print
            Console.WriteLine(collection[0].Text);
        }
    }

Great, it works now! So what was changed?

  • There is ref BigStruct variable declared – ref keyword indicates that it’s a ref local variable
  • The GetFirst call is also prefixed by ref keyword – compiler by default returns by value, the keyword instructs the compiler to return by reference.

Restrictions

Not every variable can be returned by reference. Some cases might have no sense. For example, let’s consider a local variable that we might want to return by reference. The local variable exists only when we are inside of our method. Once we return, we exit the method and the variable is destroyed.

    class Example4
    {
        ref int ReturnLocalVariable(ref int refParameter)
        {
            int localValue = 7;

            // This won't compile
            // CS8168: Cannot return local 'localValue' by reference because it is not a ref local
            // return ref localValue;

            return ref refParameter;
        }
    }

It’s also not possible to return a fixed value or null as a reference.

    class Example5
    {
        ref string ReturnFixValue(ref string refParameter)
        {
            const string MyConstValue = "ConstValue";

            // All the below won't compile
            // CS8156: An expression cannot be used in this context because it may not be passed or returned by reference

            // return ref "MyValue";
            // return ref null;
            // return ref MyConstValue;

            return ref refParameter;
        }
    }

Next, it’s not allowed on async methods.

    class Example6
    {
        int[] _values = new[] { 1, 2, 3 };

        // The method signature is not accepted by the compiler
        // CS1073: Unexpected token 'ref'

        //async Task<ref int> AsyncMethod()
        //{
        //    return ref _values[0];
        //}
    }

Finally, C# 7.0 does not allow to modify ref local variables – but it was fixed in C# 7.3.

    class Example7 : Example1
    {
        public void ReassignRefLocal()
        {
            var collection = new[]
            {
                new BigStruct
                {
                    Text = "My value"
                }
            };

            // Create ref local and assign
            ref BigStruct first = ref GetFirst(collection);

            // Reassign won't compile in C# 7.0
            // CS8107: Feature 'ref reassignment' is not available in C# 7.0. Please use language version 7.3 or greater.
            // first = ref GetFirst(collection);
        }
    }
Facebooktwittergoogle_pluslinkedinmail

Leave a comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.