Dotnet logo

.NET Tools

Essential productivity kit for .NET and game developers

How-To's

Non-trailing named arguments in ReSharper 2018.1 EAP

Whenever we pass a value to a constructor or method, named arguments can help clarify to which parameter an argument is being bound. Being quite helpful most of the times, it had one downside by times: argument names had to be added for all following arguments as well, simply to make the compiler happy. This was fixed with non-trailing argument names in C# 7.2 and we added full support for this in ReSharper 2018.1 EAP. Let’s explore what named arguments can do for us!

Improve readability

Especially for literals like true, false, null, numbers and strings, it is often hard to tell what they do. Consider this snippet:

public static void M(CancellationToken cancellationToken)
{
    var type = Type.GetType("MyNamespace.MyType", true, true);
    var delay = Task.Delay(1000, cancellationToken);
}

Looking at these invocations, it’s difficult to say what those literals actually mean. 1000 and two times true? What’s that for? We could use something like Go To Declaration (F12), ParameterInfo (Ctrl+Shift+Space) or even debugging to find out, but that’s not very convenient. Since C# 4 we can use named arguments, which is a zero-effort-solution for future readers of our code.

public static void M(CancellationToken cancellationToken)
{
    var type = Type.GetType("MyNamespace.MyType", throwOnError: true, ignoreCase: true); // already legal
    var delay = Task.Delay(millisecondsDelay: 1000, cancellationToken: cancellationToken); // already legal
}

One major drawback since C# 4 was that whenever we wanted to add the argument name for a single argument, all following arguments had to have their name added as well. Writing cancellationToken: cancellationToken is not providing us with many benefits but language noise. With the release of C# 7.2 we can make use of non-trailing named arguments and add the name only at the positions we really want. However, they still must occur in their correct position, so that the compiler can figure out to which parameter it should be bound:

public static void M(CancellationToken cancellationToken)
{
    var delay1 = Task.Delay(millisecondsDelay: 1000, cancellationToken); // became legal
    var delay2 = Task.Delay(cancellationToken, millisecondsDelay: 1000); // illegal: out-of-position
}

If you experience any syntax errors given the example above, you may need to adjust our language version.

Another good thing to know is that C# 7.2 also removes the limitation of using named arguments in combination with params arguments. A params argument allows us to pass a variable amount of arguments at the end of an invocation. The compiler then automatically converts the tail of arguments into an array of the given type. With C# 7.2, we now can add argument names before params arguments. But still, argument names cannot be added for params arguments themselves:

public class Program
{
    public static void M(Task task1, Task task2)
    {
        When(all: TaskStatus.RanToCompletion, any: TaskStatus.Faulted, task1, task2); // became legal
        When(all: TaskStatus.RanToCompletion, any: TaskStatus.Faulted, tasks: task1, task2); // still illegal
    }

    public static Task When(TaskStatus all, TaskStatus any, params Task[] tasks)
        => throw new NotImplementedException();
}

With dynamic invocations, we are unfortunately out of luck. We still cannot use named arguments preceding positional arguments:

public void SetTopLeftText(dynamic excellApp, string text)
{
  dynamic range = excellApp.Cells[1, 1];
  return range.Insert(shift: 0, text); // illegal
}

For quite some time already, ReSharper has provided code inspections and code style options to make a clear decision. They are located under Options | Code Editing | C# | Code Style. Based on the argument kind, we can choose if an argument should be positional or named. For instance, is it a literal value (true, null, numbers etc.) or string literal value ("Joe")? Is it a named expression (variable, property, etc.) or an anonymous method (lambda expression)? The category Other includes all other expression types, like conditional, null-coalescing, binary, invocation or typeof expressions.

Setting inspections severities and code style for different argument kinds

Tip: we can freely choose to which settings layer these code styles should be saved.

As soon as all settings are in place, code inspections will kick in whenever our settings are not followed. We can then use the corresponding quick-fix (even in bulk mode):

Using quick-fixes to fix code inspections

Users of code cleanup will surely like that we can also activate the option Apply argument style in our code cleanup profile. If applied to the default profile, we just need to invoke the SilentCleanupCode action (Ctrl+E, F):

Using code-cleanup to fix code inspections

As we’ve seen, named arguments can be very helpful to make our code more readable, and with non-trailing named arguments this becomes even more a pleasure. ReSharper 2018.1 EAP is ready on the starting blocks, helping us to apply this new language feature successfully in our code bases.

Download ReSharper 2018.1 EAP now! We’d love to hear your feedback!

image description