Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

C# 7 ref returns and locals

0.00/5 (No votes)
15 Dec 2019 1  
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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here