Over a million developers have joined DZone.

C# 4.0 Feature Focus - Part 2 - Named Parameters

·

In the previous episode of this feature focus series we talked about optional parameters. Today we'll cover another feature introduced in C# 4.0, named parameters. One of the most applicable places for optional and named parameters is when dealing with COM interop such as interaction with the Office automation APIs, but they can also be used as a stand-alone language feature. Just like optional parameters, named parameters are a symmetric feature: you can both consume and declare them.

The syntax

Assume the following simple subtraction method is defined:

static int Subtract(int a, int b)
{
return a - b;
}

The typical way to call this method is obviously by specifying the parameters in order, like Subtract(5, 3). However, with named parameters it's possible to write the following:

static void Main()
{
Console.WriteLine(Subtract(b: 3, a: 5));
}

Ultimately this translates into a call to Subtract(5, 3). It's clear the sample above is not very realistic (but you have to admit it's simplistic). Typically named parameters are used where optional parameters appear in the target method, but you're not interested in lots of those:

static void Bar(int a = 1, string b = null, bool c = false)
{
// ...
}

Now assume you're only interested in the last parameter, without the named parameter feature you'd have to write Bar(1, null, ...) but now you can go ahead and write:

Bar(c: true);

You might wonder why the syntax uses a colon (:) instead of an assignment equals character (=). The answer is straightforward: assignments have a value and can be used everywhere a value is expected:

bool c = false;
Bar(c = true);

This will assign true to the local variable c and feed that value in as the first argument of Bar. So colon is the way to go (and I'm not going to start religious debates about the spacing around the :, I'll leave that to the C/C++ community as those people are experienced in this kind of discussions, trying to reach an agreement on the totally irrelevant placing for the * character <g>).

The implementation

It should be clear that the implementation only affects the call site, not the caller. Here's how the Main method from above looks like:

.method private hidebysig static void  Main() cil managed
{
.entrypoint
// Code size 19 (0x13)
.maxstack 2
.locals init (int32 V_0,
int32 V_1)
IL_0000: nop
IL_0001: ldc.i4.3
IL_0002: stloc.0
IL_0003: ldc.i4.5
IL_0004: stloc.1
IL_0005: ldloc.1
IL_0006: ldloc.0
IL_0007: call int32 Program::Subtract(int32,
int32)
IL_000c: call void [mscorlib]System.Console::WriteLine(int32)
IL_0011: nop
IL_0012: ret
} // end of method Program::Main

First of all, notice the names of parameters don't appear in the call site in any way (they never have, that's not the way IL works). Ultimately we simply call Subtract with the parameters supplied in the right order. But how we get there is important to take a closer look at:

IL_0001:  ldc.i4.3
IL_0002: stloc.0
IL_0003: ldc.i4.5
IL_0004: stloc.1
IL_0005: ldloc.1
IL_0006: ldloc.

The thing to notice here are the mirrored stloc (store to local variable) versus ldloc (load from local variable) instructions. On lines IL_0002 and IL_0004 values are stored to variables 0 and 1, while on lines IL_0005 and IL_0006 they're read out in reverse order. What's happening here is that arguments to the method call are evaluated in lexical order, something that boils down to a one-liner in section 14.4.1 of the C# specification (ECMA-334):

14.4.1    Argument lists
...
The expressions of an argument list are always evaluated in the order they are written.

[Example: Thus, the example

class Test
{
static void F(int x, int y, int z) {
System.Console.WriteLine("x = {0}, y = {1}, z = {2}", x, y, z);
}

static void Main() {
int i = 0;
F(i++, i++, i++);
}
}

produces the output

x = 0, y = 1, z = 2

end example]

This becomes relevant in the context of side-effects but it should be clear by now that it's better not to rely on those kind of side-effects at all. Nevertheless, consistency is a must and hence the named parameters invocation syntax follows those rules as well. Here's the new sample you can predict the output for based on the previous observations:

class Test
{
static void F(int x, int y, int z) {
System.Console.WriteLine("x = {0}, y = {1}, z = {2}", x, y, z);
}

static void Main() {
int i = 0;
F(z: i++, x: i++, y: i++);
}
}

The caveat

This time the caveat is trivial: don't rename parameters on public methods as they might be used in conjunction with the named parameter feature. Here's a sample.

Step 1: Compile the following (csc /t:library namedlib.cs)

public static class NamedLib
{
public static int Subtract(int a, int b)
{
return a - b;
}
}

Step 2: Compile the following (csc named.cs /r:namedlib.dll)

using System;

class Program
{
static void Main()
{
Console.WriteLine(NamedLib.Subtract(b: 3, a: 5));
}
}

Step 3: Run named.exe

> named.exe
2

Step 4: Change the library and recompile

public static class NamedLib
{
public static int Subtract(int x, int y)
{
return x - y;
}
}

Step 5: Recompile the application

named.cs(7,27): error CS1739: The best overload for 'Subtract' does not have a parameter named 'b'

Names matter. Obviously reordering parameters of the same type without recompiling consumers is a breaking change too (the types of the parameters in the signature doesn't change, so the overload is still valid but semantics of the parameters have changed), but that's unrelated to the use of named parameters.

Conclusion

Named parameters provide an easy way to omit ("skip over") optional parameters and are typically used in conjunction with that particular feature. However, they can be used in isolation as well. Once more, library designers should be cautious about messing with the public methods they provide. In particular, make sure names on publicly exposed methods are stable as changes to those have the potential of breaking callers. Another reason to spend time on XML documenting public members, as you'll double-check (I hope) the <param /> tag for its name.

Oh, and did you notice the parallels with concepts in PowerShell on positional and named cmdlet arguments? The only piece missing are aliases ;-).

Next time, before we dive into more language-specific features, a short intermezzo in LINQ with a new operator: Zip. Enjoy!

Topics:

Published at DZone with permission of Bart De Smet, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}