09 September 2010

Introduction to Optional Arguments and Named Arguments

This is an introduction to Optional Arguments in C# 4.  C# programmers, beware the optional argument overload trap!  If you already know about Optional Arguments and Named Arguments, you may want to check out my post, Optional Argument Overload Bear Traps.


What is an Optional Argument?

Optional arguments (sometimes called optional parameters) are input parameters.  Input parameters prefixed with ref or out are not allowed to be optional.  The difference between a "normal" input parameter and an optional input parameter is that optional arguments have a default value.  When calling the method, simply pass no value to the optional argument, to use its default value.

Optional arguments are a means of eliminating or reducing the need to write method overloads. The C# compiler determines what overloads are actually needed, by analyzing references to the method, and then creating an overload for each argument combination actually called from other classes.  The following methods achieve the same results, after compilation:

string OverloadedMethod()
{
 return "Hello, world!";
}

string OverloadedMethod(string greeting)
{
 return string.Format("{0}, world!", greeting);
}

string OverloadedMethod(string greeting, string subject)
{
 return string.Format("{0}, {1}!", greeting, subject);
}

// The compiler converts this OptionalArgument method
// into three overloads.
string OptionalArgument(
    string greeting = "Hello", 
    string subject = "world")
{
 return string.Format("{0}, {1}!", greeting, subject);
}

Calling the following methods produces these results:
  • OverloadedMethod();
    "Hello, world!"
  • OptionalArgument();
    "Hello, world!"
     
  • OverloadedMethod("Good night");
    "Good night, world!"
  • OptionalArgument("Good night");
    "Good night, world!"
     
  • OverloadedMethod("Good night", "Gracie");
    "Good night, Gracie!"
  • OptionalArgument("Good night", "Gracie");
    "Good night, Gracie!"
     
This provides good supporting evidence that the compiler is indeed generating overload OptionalArgument methods, that match the OverloadedMethod overloads.

QUESTION:  If both greeting and subject are both optional, doesn't that mean we can provide a value to subject, and use the default greeting value?  If so, how do we do that?  We can't simply pass null, because input parameters are often value types, and are incapable of accepting a null value.  Do we have to use references and pointers, as in C++?

ANSWER: Yes, we can do that, and no, we don't have to use unsafe code, as in C++.  We can pass data to specific optional parameters by using Named Arguments.

What is a Named Argument?

A named argument is simply a means to providing values to specific input parameters.  Label syntax is used, to identify the input parameter for which the value is to be assigned.
string OptionalArgument(
 string greeting = "Hello",
 string subject = "world")
{
 return string.Format("{0}, {1}!", greeting, subject);
}

void Test()
{
 // Calling the method without named arguments:
 OptionalArgument("Greetings", "Earthling");
  // Returns "Greetings, Earthling!"

 // We only want to provide a value to "subject",
 // so, prefix the value with the "subject:" label:
 OptionalArgument(subject: "Earthling");
  // Returns "Hello, Earthling!"

 // The named argument labels allow us to list
 // parameter values in any order, too!
 OptionalArgument(subject: "Earthling", greeting: "Greetings");
  // Returns "Greetings, Earthling!"
}

Now, we are able to fully leverage the power of optional parameters.

Potential Problems

Be very careful about when you choose to create a method with optional arguments, especially when updating existing code and/or an API.  Optional arguments could limit your options to making changes in the future.  Please read my post about these issues: Optional Argument Overload Bear Traps.

Practical Uses (Updated)

OBJECT INITIALIZERS
An excellent place for implementing both optional and named arguments is with Structs.  I find structs laborious; becasue you have to doctor them up, to ensure their values are read only, have default valudes, etc.  For example:

public struct Person
{
 public readonly string Name;
 public readonly int Age;
 public readonly bool? Gender;

 Person(string name, int? age, bool? gender)
 {
  Name = (name.Length == 0 ? "Unknown" : name);
  Age = age ?? -1;
  Gender = gender;
 }
}

This works perfectly well, except we are now forced to provide a value to every argument. Employing nullable types helps, as we can simply pass a null value, but we are probably going to have to create the nullable type, just to initialize the struct.

Optional arguments circumvent the need to create the nullable types for instantiation:
public struct Person
{
 public readonly string Name;
 public readonly int Age;
 public readonly bool? Gender;

 Person(
     string name = string.Empty
    ,int age = -1
    ,bool? gender = null)
 {
  Name = name;
  Age = age;
  Gender = gender;
 }
}

Although we haven't saved much code in defining the struct, instantiating the struct becomes far more efficient.

No comments:

Post a Comment

Please provide details, when posting technical comments. If you find an error in sample code or have found bad information/misinformation in a post, please e-mail me details, so I can make corrections as quickly as possible.