Table of Contents
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);
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);
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;
pt.x = 10;
pt.y = 20;
Console.WriteLine("{0},{1}", cd.Pt.x, cd.Pt.y);
cd.Pt = pt;
Console.WriteLine("{0},{1}", cd.Pt.x, cd.Pt.y);
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);
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);
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);
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
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.
- 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.
- 16th December, 2019: First release