65.9K
CodeProject is changing. Read more.
Home

C# 7 ref returns and locals

starIconstarIconstarIconstarIconstarIcon

5.00/5 (8 votes)

Dec 15, 2019

CPOL

3 min read

viewsIcon

8250

downloadIcon

99

Easier direct memory access in a safe way

Table of Contents

Introduction

C# 7 introduced ref-local and ref-return functionality to allow safe direct-memory access to value variables. Before C# 7, we could do it in an unsafe code but now is available to access in a safe way. This is an example of ref-local variable taking the address of the a variable. b is behaving like an alias variable of a, note the use of ref keyword on both sides of the b initialization!

int a = 10;
ref int b = ref a;
b = 20;
Console.WriteLine("{0}", a); // display 20

I visualize the equivalent C++ code as such. b is a C++ reference. Like C++ reference, ref-local variable cannot be reassigned to another variable after initialization.

int a = 10;
int& b = a;
b = 20;
printf("%d", a); // display 20

ref-return on Value Member

For the ref-return on the property, we'll use the classes below for our example. Please note that Point is structure, therefore a value type, as opposed to the reference type like Coordinate. We can only use ref-return from reference type property because structure methods cannot ref-return its instance fields. Anyone who needs a refresher on difference between .NET reference and value types can refer to this useful link.

struct Point
{
    public int x;
    public int y;
}
    
class Coordinate
{
    private Point _Point;
    public Point Pt
    {
        set { this._Point = value; }
        get { return this._Point; }
    }
    public ref Point RefPt
    {
        get { return ref this._Point; }
    }
}

As we can see, the normal Pt property has a setter while RefPt ref-return property doesn't. The reason is due to RefPt getter directly exposing _Point for outside modification once it is ref-returned. Let's first see how Pt property is normally used.

Coordinate cd = new Coordinate();
Point pt = cd.Pt; // a copy
pt.x = 10;
pt.y = 20;
Console.WriteLine("{0},{1}", cd.Pt.x, cd.Pt.y); // display 0,0
cd.Pt = pt;
Console.WriteLine("{0},{1}", cd.Pt.x, cd.Pt.y); // display 10,20

Next, we'll see how value RefPt property is normally used.

Coordinate cd = new Coordinate();
ref Point pt = ref cd.RefPt;
pt.x = 10;
pt.y = 20;
Console.WriteLine("{0},{1}", cd.Pt.x, cd.Pt.y); // display 10,20

If the ref keyword is (accidentally) omitted from both sides of the initialization, ref-return will be instead copied to the pt variable, thus _Point shall remained unmodified.

Coordinate cd = new Coordinate();
Point pt = cd.RefPt;
pt.x = 10;
pt.y = 20;
Console.WriteLine("{0},{1}", cd.Pt.x, cd.Pt.y); // display 0,0

Assuming you can directly access/modify _Point member by changing to public accessibility. The code will be like this:

Coordinate cd = new Coordinate();
cd._Point.x = 10;
cd._Point.y = 20;
Console.WriteLine("{0},{1}", cd.Pt.x, cd.Pt.y); // display 10,20

Benchmark

We'll benchmark the ref-return against value property access and public member direct access. This shall be the benchmark code of looping 10 million Coordinate objects.

Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();

for(int i=0; i < list.Count; ++i)
{
    Point pt = list[i].Pt;
    pt.x = 10;
    pt.y = 20;
    list[i].Pt = pt;
}

stopWatch.Stop();
DisplayElapseTime("Value Return RunTime:", stopWatch.Elapsed);

Stopwatch stopWatch2 = new Stopwatch();
stopWatch2.Start();

for (int i = 0; i < list.Count; ++i)
{
    ref Point pt = ref list[i].RefPt;
    pt.x = 10;
    pt.y = 20;
}

stopWatch2.Stop();
DisplayElapseTime("Ref Return RunTime:", stopWatch2.Elapsed);

Stopwatch stopWatch3 = new Stopwatch();
stopWatch3.Start();

for(int i=0; i < list.Count; ++i)
{
    list[i]._Point.x = 10;
    list[i]._Point.y = 20;
}

stopWatch3.Stop();
DisplayElapseTime("Public Member access RunTime:", stopWatch3.Elapsed);

The benchmark result is below. ref-return performance is 50% over the traditional value return access and is 20% better than the public member access! The difference in timing is more pronounced when more fields are added to Coordinate class.

Value Return RunTime:00:00.040
Ref Return RunTime:00:00.019
Public Member access RunTime:00:00.025

Conclusion

After seeing ref-return in action, I must stress that the rules for ref-return functionality must be followed.

  • The result of a regular method return value cannot be assigned to a ref local variable. However, ref-return values can be implicitly copied into non-ref variables.
  • You cannot return a ref of a local variable because the actual memory must persist beyond the local scope to avoid invalid memory access.
  • A ref variable cannot be reassigned to a new memory location after initialization.
  • Struct methods cannot ref-return instance fields.
  • This functionality cannot be used with async methods.

This feature is most useful for the situations I described below:

  • Modifying fields in a property-exposed struct
  • Directly accessing an array location
  • Repeated access to the same memory location

Code examples are hosted at Github.

Reference

  • Writing High-Performance .NET Code, 2nd Edition by Ben Watson. A must-read book for those .NET code efficiency aficionados. I am not affiliated with Amazon, meaning I get no kickback when you buy from that link, so feel free to browse that webpage. Please note that some typos are in the ref-return code examples in the book, so when you copied the code and it fails to compile, be sure to check the official C# guide.

History

  • 16th December, 2019: First release