Dotnet logo

.NET Tools

Essential productivity kit for .NET and game developers

How-To's

State of the union: ReSharper C# 7 and VB.NET 15 support

When we released ReSharper 2016.3 (and earlier this week, ReSharper 2017.1 EAP), we introduced initial support for C# 7 and VB.NET 15. For example, ReSharper now understands binary literals and digit separators, as well as local functions with several inspections. We’re far from feature complete, but want to look ahead at what’s coming in terms of new programming language features and what we are working on for ReSharper (including some of the challenges building out those features). Here goes!

In this post:

Digit separators and binary literals

Both C# 7 and VB.NET 15 allow _ to occur as a digit separator inside numeric literals. This helps improve code readability. For example, adding digit separators in the following snippet clearly shows our original code may contain a bug (or a bad variable name – naming is hard!)

var million = 10000000; // no digit separators
var million = 10_000_000; // digit separators added

ReSharper has a quick-fix which lets us add digit separators to any numeric literal:

ReSharper support for C# digit separators

Note that ReSharper adds digit separators at the expected location (thousands separator), but it’s perfectly possible to place them anywhere in a number:

var number = 17_15_1337;

Digit separators also are a language onramp for binary literals. It’s now possible to specify bit patterns directly instead of having to know hexadecimal notation by heart.

byte b = 0b1010_1011;

ReSharper can easily convert a number to binary notation and back, too:

Binary literals and quick-fix to convert between formats

ReSharper 2016.3 already includes support for C# 7 and VB.NET 15 binary literals and digit separators.

Local functions

When coding, we often have to create a helper function. Sometimes, such helper function should only be available for one single method, so it would make sense to add it inside that method. That’s exactly what local functions are. With C# 7, we can declare helper functions inside other function bodies as a local function:

public static int Factorial(int number)
{
    int Multiply (int num)
    {
        return (num == 1)
                  ? 1
                  : num * Multiply(num - 1);
    }

    return Multiply(number);
}

ReSharper already provides some support for local functions, and provides a basic set of code inspections for them, such as Possible System.NullReferenceException, Access to disposed/modified closure, and more. We also updated some of the existing refactorings and quick-fixes we have, such as this one where we can convert a statement body to an expression body, and back.

Convert local function to expression body and vice-versa

This already existed for C# 6.0, but now it works for C# 7.0 local functions, too. Note in this video we can also see that a recursive call was detected in our local function.

Local functions are comparable to lambda functions, but they are a bit more readable and quite useful if we want to write a function that is called only within the context of another method. In our ReSharper 2017.1 EAP, we added two new quick-fixes that allow converting a lambda expression or anonymous function to a local function:

Convert to local function quick-fix

Our codebase will mature, and the local function may have to become a proper method of its own. No problem! We have you covered on the refactoring front:
Convert local function into a regular method

Support for local functions is still under development. And not only on our side – the language design itself isn’t fully carved in stone either – C# 7.0 is still evolving. For example, we had to wait for a PR from the C# compiler team before we knew whether supporting [NotNull]/[CanBeNull] annotations with local functions to help static analysis would be possible.

Output variables

In C#, using out parameters is not very fluid. For example, we always have to declare a variable with its full type before we can do a method call using that variable:

int number;
if (int.TryParse("42", out number)) {
  // ... work with number ...
}

C# 7 introduces output variables, making it possible to declare a variable right at the point where we’re making our method call:

if (int.TryParse("42", out int number)) {
  // ... work with number ...
}

While ReSharper 2016.3 already recognizes output variables, there are some things we’re improving. An example would be detection of output variable scope. In the above code snippet, the variable number becomes available in the outer scope. This means we can use the number variable outside of our if statement as well. With a loop like for, foreach or while, this does not happen though.

while(int.TryParse(Console.ReadLine(), out int x))
{
    // ...
}
Console.WriteLine(x); // <-- x not available here

We keep a very close eye on the .NET repo on GitHub to make sure we’ll have fantastic C# 7 support in ReSharper!

Other things we are working on are navigation to the out var‘s type – navigation to out variable itself already works in ReSharper 2016.3.

C# and VB.NET Tuples

What options are there today to return more than one value from a method? We can use out parameters, but they are not very pleasant to work with and not supported when writing async methods. We can use System.Tuple<...> as a return type, but those allocate an object. On top of that, consuming code then has to put up with knowing what the Item1, Item2 etc. properties mean. Specific types or anonymous types are two other options, but those are not very efficient to write/run either.

Both C# 7 and VB.NET 15 now adds tuple return types and tuple literals. For example:

public (string, string) FindPersonName(int id)
{
    // ... lookup data ...

    return (firstName, lastName);
}

Or in VB.NET:

Public Function FindPersonName(Id As Integer) As (String, String)
    '... lookup data ...

    Return (FirstName, LastName)
End Function

The calling code will get back a tuple with two string properties, which we can then work with:

var personNames = FindPersonName(123);
var firstName = personNames.Item1;
var lastName = personNames.Item2;

That’s… nice, but not very. We’re still using Item1 and Item2 here. Again, C# 7 and VB.NET 15 to the rescue! We can update our FindPersonName method and name our tuple elements:

public (string FirstName, string LastName) FindPersonName(int id)
{
    // ... lookup data ...

    return (firstName, lastName);
}

The consuming code could now look like this:

var personNames = FindPersonName(123);
var firstName = personNames.FirstName;
var lastName = personNames.LastName;

Much better! We’re building support for tuples in ReSharper, adding and updating inspections, quick-fixes and refactorings. One of those refactorings is Transform parameters”, where we will allow converting a method utilizing out parameters to a method using multiple return values via tuples. As with all new language features, some of them are still moving targets. For example if C# adds support for tuples in using directive, we will, too. To be continued!

Deconstruction

In the above example with tuples, we are still using an intermediate tuple, personNames, which we’re then assigning to a firstName and lastName local variable. C# 7 introduces deconstruction – a clean way of consuming tuples. New syntax has been introduced for splitting a tuple into its parts and assigning those parts to new variables:

(string firstName, string lastName) = FindPersonName(123);

// ... work with firstName and lastName ...

Deconstruction not only works for tuples. It works for any type, as long as that type implements the Deconstruct method, either directly or via an extension method. For example, we could write an extension method for KeyValuePair<TKey, TValue> which lets us use the key and value directly – avoiding having to use .Key/.Value expressions.

// Extension method
public static class KVPExtensions
{
  public static void Deconstruct<TKey, TValue>(
      this KeyValuePair<TKey, TValue> kvp,
      out TKey key,
      out TValue value)
  {
    key = kvp.Key;
    value = kvp.Value;
  }
}

// Consuming a dictionary
foreach (var (key, value) in dictionary)
{
    var item = $"{key} => {value}";
}

We can add multiple overloads for the Deconstruct method, taking a different number of out parameters. This lets us support deconstructing into various different variable sets.

ReSharper currently does not support deconstruction yet.

Pattern matching

This is one feature I personally like a lot. C# 7 adds new syntactic elements that allow us to test whether a variable has a certain “shape” or type and then start working with it. Here’s an example:

public void Dump(object data)
{
    if (data is null) return;
    if (data is int i) Console.WriteLine($"Integer: {i}");
    if (data is float f) Console.WriteLine($"Float: {f}");
}

In the above sample, we are doing several pattern matches. A constant pattern match checks whether data is null. The next two checks are type pattern matches, testing data has a certain type and immediately extracting the value into a new variable with that type. Nice, no? Variables introduced by a pattern are similar to output variables described in that they can be declared in the middle of an expression and can then be used within the nearest surrounding scope.

Another nice example where we’re using patterns and output variables together to check, cast and parse all at once:

if (data is int i || (data is string s && int.TryParse(s, out i))
{
    // ... use i here ...
}

ReSharper 2016.3 already partially supports pattern matching for is expressions and switch statements, although we’re still working on supporting a really nice feature of pattern matching where we can write code that checks for type information as well as specific property values using the when keyword:

switch (vehicle)
{
  case Car lada when (lada.Make == "Lada"):
    Console.WriteLine("<Lada car>");
    break;

  case Car car:
    Console.WriteLine("<generic car>");
    break;

  case Van van:
    Console.WriteLine("<generic van>");
    break;

  default:
    Console.WriteLine("<unknown vehicle>");
    break;

  case null:
    throw new ArgumentNullException(nameof(vehicle));
}

ReSharper does not yet fully understand this syntax – we’re working on that and looking at making our existing code inspections and quick-fixes aware of new C# 7 syntax. We’ll have to recognize x is null as a null check, suggest using an is expression instead of an as + null check, …

Others

C# 7 and VB.NET 15 come with some additional language enhancements that we’re also adding support for in ReSharper but are still in early stages in our nightly builds (nightlies, one of the perks of working at JetBrains!)

We’re working on properly parsing throw expressions and wiring this into existing inspections, quick-fixes and refactorings.

class Employee
{
    public string Name { get; }
    public Employee(string name) => Name = name ?? throw new ArgumentNullException(nameof(name));
}

In our code we could already pass variables by reference (using ref / ByRef). C# 7.0 and VB.NET 15 now also let us return from a method by reference.

public ref int Find(int number, int[] numbers); // return reference

ref int index = ref Find(7, new[] { 1, 2, 3, 7, 9, 12 }); // use reference
Default Public ReadOnly Property Item(index As Integer) As ByRef Integer

Support for ref / ByRef returns is something we’re working on, too.

Many of our existing features have to be adapted or even rethinked to work with a new language version (and of course, should remain compatible with older language versions). We need to make sure existing ReSharper suggestions and inspections still make sense and produce correct and idiomatic C# code.

Our C# 7.0 and VB.NET 15 support is definitely not complete yet. We did want to provide an overview of what the programming language is evolving to and what we are currently working on in terms of supporting that. With ReSharper 2016.3, we provide early support for:

  •  C# 7.0/VB.NET 15 binary literals and digit separators (parsing, several context actions, support for literals written in different base/separated differently)
  • Support for C# local functions (parsing, analyzing null)
  • Limited support for C# pattern-matching expressions (as the language design itself is not finished), C# output variables, C# tuples and C# deconstruction

All work in progress, but if you are using any of the new language features we’d love to hear your feedback! Download ReSharper Ultimate 2016.3, or even ReSharper Ultimate 2017.1 EAP, and give it a try!

image description