I’ve actually never had or felt a need to use named arguments in C#. Am I missing anything?
Well first off I have actually used named arguments in other languages and my immediate feeling was – this makes my code so verbose and I felt I got no real benefit from them. Certainly they can (sort of) document your code a little better, but with most/all development tools including intellisense or other tools to tell the development what parameters they were editing/adding and in some cases even show the named of the argument in the editor – hence the benefit of “documenting” seemed to be of little real use.
This all said, let’s look at what we can do with named arguments.
What are named arguments?
When we write methods/functions we pass arguments/parameters using “positional arguments”, which simply means the order of arguments must match the method signature’s argument order. For example let’s look at a simple method to add a person to some application
void AddPerson(string name, string address, int age) { // do something }
So when we use this method with positional arguments we would write
AddPerson("Scooby Doo", "Mystery Machine", 11);
In C# we also get the ability to use named arguments instead (without any changes to the method signature) by including the argument name as part of the call, so for example
AddPerson(name: "Scooby Doo", address: "Mystery Machine", age: 11);
With tools like Visual Studio 2019, this doesn’t really add anything useful (if we’re mirroring the argument positions) because Visual Studio already tells us the name of each argument in the editor. Obviously outside of Visual Studio, for example is source control, maybe this is more useful.
Surely there’s more to it than that?
Positional arguments are just that, the calling code must supply each argument in the correct position and whilst we can do the same with named arguments, you can also rearrange the arguments and hence no longer need to call using the same positions, for example let’s switch around the named arguments from earlier to give us this
AddPerson(name: "Scooby Doo", age: 11, address: "Mystery Machine");
The C# compiler will simply rearrange the arguments into their positions producing the same IL as is generated for a callee using positional arguments. Here’s an example of such code generated via dotPeek – it’s exactly the same code as for the positional arguments as one would expect.
IL_0013: ldstr "Scooby Doo" IL_0018: ldstr "Mystery Machine" IL_001d: ldc.i4.s 11 // 0x0b IL_001f: call void NameParamsTests.Program::AddPerson(string, string, int32) IL_0024: nop
One area where named arguments offer some extra goodness is when we’re using optional argument, so let’s assume our AddPerson signature changes to
static void AddPerson(string name, string address = null, int age = Int32.MinValue) { // do something }
If we’re using positional arguments and we don’t have an address then we must still supply a value for the address, for example
AddPerson("Scooby Doo", null, 11);
But as we’ve seen, with named arguments the order is no longer a limiting factor, therefore we can used named arguments instead and no even bother with the address, the compiler will figure it out for us, hence we can write
AddPerson(name: "Scooby", age: 11);
Note: We can ofcourse use positional and named arguments in a method call if we wish/need to but then the named arguments would need to be in the correct position limiting the usefulness of using named arguments.
Named arguments – when we have lots of arguments
The simple AddPerson method probably isn’t a great example for using named arguments, so lets instead look at a method that takes more arguments with lots of optional arguments. If we instead have a method which looks like this
void AddPerson( string firstName, string lastName, int age = Int32.MinValue, string addressLine1 = null, string addressLine2 = null, string addressLine3 = null, string city = null, string county = null, string postalCode = null) { // do something }
Now we can see that if we have partial details for the person we can call this method in a more succinct manner, for example
AddPerson(age: 11, firstName: "Scooby", lastName: "Doo", postalCode: "MM1"); // or with mixed positional and named arguments AddPerson("Scooby", "Doo", 11, postalCode: "MM1");
As you’d imagine, the compiler simply handles the setting of the optional arguments etc. as before giving us IL such as
IL_0001: ldstr "Scooby" IL_0006: ldstr "Doo" IL_000b: ldc.i4.s 11 // 0x0b IL_000d: ldnull IL_000e: ldnull IL_000f: ldnull IL_0010: ldnull IL_0011: ldnull IL_0012: ldstr "MM1" IL_0017: call void NameParamsTests.Program::AddPerson(string, string, int32, string, string, string, string, string, string) IL_001c: nop
Once our methods start getting more arguments and especially if lots are defaulted, then named arguments start to make sense, although with a larger number of arguments, one might question whether in fact the method call itself might be in need of refactoring, with our example here we could ofcourse create separate objects for different parts of the data and with C#’s object initializer syntax we sort of get a similar way to create “named” arguments, for example
public struct Person { public string FirstName { get; set; } public string LastName { get; set; } public int Age { get; set; } public string Line1 { get; set; } public string Line2 { get; set; } public string Line3 { get; set; } public string City { get; set; } public string County { get; set; } public string PostalCode { get; set; } } void AddPerson(Person person) { // do something }
Now using object initializer syntax we could call this method like this
AddPerson(new Person { FirstName = "Scooby", LastName = "Doo", Age = 11, PostalCode = "MM1" });