Dotnet logo

.NET Tools

Essential productivity kit for .NET and game developers


Leading digit separators, ref structs and in parameters – C# 7.2 in Rider and ReSharper

Time for another blog series! This time we will focus on the new C# 7.2 language features for which we’ve added support in ReSharper 2018.1 and Rider 2018.1. We will see how they help to improve our code quality and how they work under the hood.

In this series:

Let’s get started with something easy …

Leading digit separators in numeric literals

Beginning with C# 7.0, we can use underscores to separate large numeric literals into chunks of digits – thus called digit separators. Using digit separators can help to improve readability in our code base:

// before C# 7.0
var a = 1000000000;
var b = 0x001111000011;

// with C# 7.0
var c = 1_000_000_000;
var d = 0x0011_1100_0011;

Prior to C# 7.2, digit separators were only permitted after the first significant digit of a hexadecimal or binary literal. This limitation has been removed so that we can now use the first group solely to specify the base (or radix) of our numeric literal. Other limitations from C# 7.0 remain the same, including that underscores cannot occur next to the decimal, next to the exponent character or next to the type specifier.

// became valid with C# 7.2
var a = 0b_1_1_0;
var b = 0x_ff_ff_00;

// still invalid
var c = 10_.0;  // error: next to decimal
var d = 1.1e_1; // error: next to exponent character
var e = 10_f;   // error: next to type specified

Speaking of numeric literals, we hope you didn’t miss our several new context actions for converting between different bases and adding/removing digit separators:

Converting numeric literals and adding digit separators

Ref structs

In order to understand ref structs, we first have to understand the concept of value types. Structs, like bool, int or char, are value types. They are a more or less a cut-down version of classes, which doesn’t allow inheritance (though they implicitly derive from System.ValueType) or finalizers. Also, a variable of value type cannot be assigned as null (we have to use nullable types for that).

When passing a local variable with value type to another method, in reality a copy of this value is passed; so called by-value. This is the opposite of the by-reference approach, where changes in the called method will also be reflected in the call-site:

void M()
    var i = 5;
    N(ref i);
    Console.WriteLine(i); // Outputs 10

void N(ref int i)
    i = 10;

Another characteristic of value types is that they are accessed directly (instead of via pointers) and that they’ll be usually allocated on the stack. This behavior can greatly improve our performance. However, there are cases in which values types can be allocated on the managed heap: whenever we box a value type (e.g. to object or an interface type) or when we declare them as a static or instance member in a reference type. As a result, we might not gain the performance benefit we’ve expected.

Boxing allocation when value type is converted to object

Note: we can use the Heap Allocation Viewer plugin, to find out about various allocations. This plugin is also available for Rider.

Luckily, ref structs can help in high performance scenarios. A ref struct looks like a normal struct, but with a ref modifier in front of it. When used, the compiler will produce an error for any operation that would expose the object to the heap (for older compilers, it generates an ObsoleteAttribute on object base classes, like GetHashCode or ToString). Think of it as a stack-only struct:

Ref structs cannot be converted to object

Ref structs can also be readonly structs, which we will look at a little later.

In parameters

We’ve already seen how value types can be passed by-reference, and how this can change the value on call-site. While reducing the amount of allocations, we are also exposing ourselves to possible mutations of our local. There is no guarantee that it will keep the initial value we’ve assigned. ReSharper has always notified about this potential issue (and reassignments in general) by highlighting the local in bold (need to enable Options | Highlight Identifiers in Options | Inspection Settings):

Highlighting of modified parameters

Fortunately, C# 7.2 introduces so-called in parameters. We can think of them as readonly ref parameters, which allow to gain all performance improvements while at the same time ensuring that the method (and sub calls) cannot modify the parameter:

public void M(in int number)
    number = 5; // error!

As described, the parameter becomes immutable within the declaring method. However, it can still be changed from the outside, for instance when working with several threads. Effectively, the value of an in parameter can still change between multiple accesses.

It also worth to notice that we can pass parameters with or without the in modifier. However, adding it will enforce our expectation on call-site and prevent changing semantics. The in modifier is also part of the method signature and therefore influences overload resolution:

Overload resolution with in parameters

There are also cases where in parameters cannot be used: entry point declarations (static void Main), async methods and iterator methods.

Using in parameters can be quite helpful. However, there is one remaining issue with them that we will examine in the next post. Stay tuned!

Download ReSharper 2018.1 now! Or give Rider 2018.1 a try. We’d love to hear your feedback!

image description

Discover more