Pass structs by reference in C♯ 7.2

Rex Ng
3 min readMar 24, 2018

--

There is an interesting feature that is worth mentioning in C♯ 7.2. As most long time C♯ programmers may know, parameter passing in C♯ is passed by value by default. That is to say, a method call Foo(T) will:

  • Copy the value of T once into memory and pass that copy into Foo if T is a value type (int, enum, struct, etc.)
  • Copy the value of the reference to T and passes it into Foo if T is a reference type ( object, class, string, etc.)

The only exceptions before C♯ 7.2 are the out and ref keywords, which changes the parameter passing semantics to pass by reference. I will not go into details on the difference between out and ref here because they are quite similar. This should not be unfamiliar to C♯ programmers as one of most common operations: checking if a key exists in a Dictionary<TKey, TValue> and simultaneously returning the value if the key exists, is implemented in the BCL’s System.Collections.Generic.Dictionary<TKey, TValue>.TryGetValue(TKey key, out TValue value), and it contains an out parameter.

Difference in memory impact between reference and value types

  • The size of a reference value is 4 bytes when running in a 32-bit process and 8 bytes when running in a 64-bit process
  • The size of a value type depends on the fields it holds

As an example, let us consider this:

  • When UseReferenceInt is called, an 4 byte (on a 32-bit process)/8 byte (on a 64-bit process) reference value is copied
  • When UseStructInt is called, exactly 4 bytes are copied since StructInt wraps only a single int, which is an alias of System.Int32, a 32-bit integer

In this specific scenario, using a struct always gives you an equal (in a 32-bit process) or even smaller (in a 64-bit process) memory footprint.

Drawbacks on using structs

However, one important aspect to consider on using struct is that size matters. If your struct contains four int fields for example, passing it to method calls incurs a 16-byte copy, in contrast to a 4/8-byte copy if this were a class.

That is why the .NET Framework Design Guidelines mentions that

AVOID defining a struct unless the type has all of the following characteristics:

It logically represents a single value, similar to primitive types (int, double, etc.).

It has an instance size under 16 bytes.

It is immutable.

It will not have to be boxed frequently.

In all other cases, you should define your types as classes.

Because copying a large struct to method call(s) is potentially memory-expensive, and it has a snowballing effect when the struct has to be passed multiple times into multiple methods.

However, this is all changed in C♯ 7.2.

Passing an (immutable) struct by reference

Consider a variation of the example above, now with four integers instead of one:

There are quite a number of differences here I would like to highlight:

  1. Both ReferenceInt and StructInt contain four integers instead of one, as mentioned above
  2. [C♯ 7.2 feature] StructInt is marked as readonly on line 11
  3. [C♯ 7.2 feature] In order to mark StructInt as readonly, it must be immutable as required by the compiler. All fields are upgraded to get-only properties as a result
  4. [C♯ 7.2 feature] UseStructInt has an in keyword specified before the StructInt parameter type, indicating that this parameter should be passed by reference
  5. [C♯ 7.2 feature] As a result of 4, the call site (line 39) is also required to include the in keyword when calling the UseStructInt method

The result is really best of both worlds. You get value type semantics (no null, no GC pressure) without the memory penalty due to copying, because the large StructInt is now passed by reference which only involves 4/8 bytes of copying, in contrast to 16 bytes if it were not passed by reference.

Why not use ref instead?

I believe you can achieve the same result by using the ref keyword in UseStructInt and keeping StructInt non-readonly. However, I wholeheartedly agree with the open-source community who suggests the inclusion of an extra keyword, in.

The in and readonly keywords remind programmers that mutable structs are (usually) evil by raising a compile error if the struct in question is mutable and forces the author to think carefully before allowing the struct to be passed by reference. This is a wise move in my opinion.

If you would like to know more, I suggest reading this blog article before blindly changing all your structs to readonly: http://faithlife.codes/blog/2017/12/in-will-make-your-code-slower/

--

--

Rex Ng
Rex Ng

Written by Rex Ng

Programmer | Watch enthusiast

No responses yet