Using C# .NET 4.0 named arguments and optional parameters

February 6, 2010

The new C# 4.0 features, named arguments and optional parameters, provide a great amount of flexibility and readability in how methods are defined and called. This article demonstrates specifically how named arguments and optional parameters (parameters with default values) help us clean up and improve overloaded methods with a lot of parameters of the same type.

First, an example

Below is a somewhat contrived and exaggerated example, but consider a helper class that allows us to send an email by calling a SendMail method with a number of overloads to give callers the most flexibility possible in determining which parameters to utilize:

public class MailHelper
{
    public static void SendMail(string subject, string body)
    {
        SendMail(subject, body, true /*default isHtml = true*/);
    }
    public static void SendMail(string subject, string body, bool isHtml)
    {
        SendMail(subject, body, null, null, isHtml);
    }
    public static void SendMail(string subject, string body, string from, string to)
    {
        SendMail(subject, body, from, to, true /*default isHtml = true*/);
    }
    public static void SendMail(string subject, string body, string from, string to, bool isHtml)
    {
        SendMail(subject, body, from, to, null, null, isHtml);
    }
    public static void SendMail(string subject, string body, string from, string to, string cc, string bcc)
    {
        SendMail(subject, body, from, to, cc, bcc, true /*default isHtml = true*/);
    }
    public static void SendMail(string subject, string body, string from, string to, string cc, string bcc, bool isHtml)
    {
        if (string.IsNullOrEmpty(to))
            to = "defaultTo@email.com";

        if (string.IsNullOrEmpty(from))
            from = "defaultFrom@email.com";

        //create and send email here...
    }
}

This is certainly a little ugly, but a paradigm used all over the .NET framework and other API methods. The effort here pays off a little for callers of this method. There’s only one method and Intellisense gives a great overview of the options provided:

method overloading intellisense

The real problem rears it’s ugly head when the arguments passed into the method aren’t clear what they represent. Take for example, the six different ways this method might be called:

string mailSubject = "Check out .NET 4";
string mailBody = "body...";

MailHelper.SendMail(mailSubject, mailBody);
MailHelper.SendMail(mailSubject, mailBody, true);
MailHelper.SendMail(mailSubject, mailBody, "a@b.com", "b@a.com"); 
MailHelper.SendMail(mailSubject, mailBody, "a@b.com", "b@a.com", false); 
MailHelper.SendMail(mailSubject, mailBody, "a@b.com", "b@a.com", "c@d.com", "d@c.com"); 
MailHelper.SendMail(mailSubject, mailBody, "a@b.com", "b@a.com", "c@d.com", "d@c.com", false);

Again, this is exaggerated, but demonstrates a real problem. What does true mean on line 5? Does the third argument in line 5 correspond to the same parameter as the seventh argument in line 9, or do these booleans represent different things? Which of these email addresses represent To, From, CC and BCC?

Using named arguments to improve clarity, flexibility

With C#/.NET 4.0, we can use named arguments to make the intent of the previous code much more clear. The convention is to specify the parameter name with a colon prior to the argument, such as [parametername]: [argument]. Let’s take the second call to SendMail from above and make it clear what true represents:

MailHelper.SendMail(mailSubject, mailBody, isHtml: true);

It’s that easy, and doesn’t require the method definition to be altered at all. Now it’s abundantly clear what we’re doing: Sending an email with a given subject and body that is formatted in html.

The only rule here is that named arguments must appear after all fixed (normal) arguments. Named arguments themselves can be specified in any order, however!

MailHelper.SendMail(subject: mailSubject, mailBody); //wont compile
//error: Named argument specifications must appear after all fixed arguments have been specified.

MailHelper.SendMail(mailSubject, body: mailBody); //valid
MailHelper.SendMail(subject: mailSubject, body: mailBody); //valid 
MailHelper.SendMail(body: mailBody, subject: mailSubject); //also valid!

As a final example, let’s take the previous SendMail method call that specified all 7 possible arguments, and use named arguments to make it abundantly clear what each argument value represents:

//unclear
MailHelper.SendMail(mailSubject, mailBody, "a@b.com", "b@a.com", "c@d.com", "d@c.com", false); 

//clear
MailHelper.SendMail(subject: mailSubject,
                    body: mailBody,
                    from: "a@b.com",
                    to: "b@a.com",
                    cc: "c@d.com",
                    bcc: "d@c.com",
                    isHtml: false);

Using optional parameters with default values to reduce the amount of method overloading

While named arguments gave us some improvements to the way a method can be called, optional (or default) parameters give us a huge benefit in how a method is declared and implemented. In the case of method overloading, it gives us the chance to reduce the amount of method signatures greatly, as well.

Consider the first two SendMail methods. The second requires a subject, body, and boolean for isHtml, while the first method only requires a subject and body, calling the second with isHtml as true automatically. This is how  you would produce the effect of having a “singular” method with an “optional” parameter, isHtml, in previous versions of C#. With optional parameters and default vallues in C# 4.0, it’s entirely unnecessary:

//no longer needed
//public static void SendMail(string subject, string body)
//{
//    SendMail(subject, body, true /*default isHtml = true*/);
//}
public static void SendMail(string subject, string body, bool isHtml = true)
{
    SendMail(subject, body, null, null, isHtml);
}


//both callers work as before
MailHelper.SendMail(mailSubject, mailBody);
MailHelper.SendMail(mailSubject, mailBody, true);

optional parameters intellisense

So, just by turning isHtml into an optional parameter, we can eliminate three of our overloaded methods without requiring any change to the method signatures or callers.

//new and improved
public class MailHelper
{
    public static void SendMail(string subject, string body, bool isHtml = true)
    {
        SendMail(subject, body, null, null, isHtml);
    }
    public static void SendMail(string subject, string body, string from, string to, bool isHtml = true)
    {
        SendMail(subject, body, from, to, null, null, isHtml);
    }
    public static void SendMail(string subject, string body, string from, string to, string cc, string bcc, bool isHtml = true)
    {
        if (string.IsNullOrEmpty(to))
            to = "defaultTo@email.com";

        if (string.IsNullOrEmpty(from))
            from = "defaultFrom@email.com";

        //create and send email here...      
    }
}

Two steps forward, one step back

You’d be right to notice that isHtml isn’t the only parameter which could be made optional – all of them could. Unfortunately, we do run into some complications the further we optionalize these parameters. This is best shown by example, so let’s make the cc and bcc parameters optional by specifying null as their default value:

//only showing the 3 method signatures
public static void SendMail(string subject, string body, bool isHtml = true)
public static void SendMail(string subject, string body, string from, string to, bool isHtml = true)
public static void SendMail(string subject, string body, string from, string to, string cc = null, string bcc = null, bool isHtml = true)

This compiles, but since the last two methods have the same required parameters, it would make the following call ambiguous between the two of them:

//no longer valid
MailHelper.SendMail(mailSubject, mailBody, "a@b.com", "b@a.com");

This is only a problem for this particular implementation, but its necessary to show that if you intend to refactor an existing group of overloaded methods in this way, there will be a time when you have to either stop, or reduce the number of possible method signatures that callers may use, if the callers aren’t using named arguments. If they are, then this isn’t a problem at all!

Putting it all together

Let’s go all the way and make everything but the subject and body optional:

public class MailHelper
{
    public static void SendMail(string subject, string body, string from = "defaultFrom@email.com", string to = "defaultTo@email.com", string cc = null, string bcc = null, bool isHtml = true)
    {
        //create and send email here...    
    }
}

optional parameters default values intellisense

Now take a look at just some of the possible ways the SendMail method can be called:

MailHelper.SendMail(mailSubject, mailBody, isHtml: true);

MailHelper.SendMail(mailSubject, mailBody, cc: "c@d.com", bcc: "d@c.com");

MailHelper.SendMail(mailSubject, mailBody, to: "a@b.com", cc: "c@d.com", isHtml: false);

MailHelper.SendMail(subject: mailSubject,
                   body: mailBody,
                   from: "a@b.com",
                   to: "b@a.com",
                   cc: "c@d.com",
                   bcc: "d@c.com",
                   isHtml: false);

So that’s named arguments and optional parameters, just one of the cool new features of C# / .NET 4.0.  Optional parameters helped us take a class with six overloaded methods in 30 lines of code, down to a single method in 4 lines of code, and named arguments allowed us to call these methods with improved clarity and flexibility.

Add comment


(Will show your Gravatar icon)


About me

I'm Kurt, and I make web applications. Want to know more? Check out my about page.

WTF is all this code? I came here for food!

My wife made a new year's resolution to try out at least one new recipe each week. Want to know what she's been feeding me? resolutionfood.blogspot.com