Close this search box.

C# Fundamentals: Optional Parameters – Pros and Pitfalls

When Microsoft rolled out Visual Studio 2010 with C# 4, I was very excited to learn how I could apply all the new features and enhancements to help make me and my team more productive developers.

Default parameters have been around forever in C++, and were intentionally omitted in Java in favor of using overloading to satisfy that need as it was though that having too many default parameters could introduce code safety issues.  To some extent I can understand that move, as I’ve been bitten by default parameter pitfalls before, but at the same time I feel like Java threw out the baby with the bathwater in that move and I’m glad to see C# now has them.

This post briefly discusses the pros and pitfalls of using default parameters.  I’m avoiding saying cons, because I really don’t believe using default parameters is a negative thing, I just think there are things you must watch for and guard against to avoid abuses that can cause code safety issues.

Pro: Default Parameters Can Simplify Code

Let’s start out with positives.  Consider how much cleaner it is to reduce all the overloads in methods or constructors that simply exist to give the semblance of optional parameters.  For example, we could have a Message class defined which allows for all possible initializations of a Message:

   1: public class Message
   2: {
   3:     // can either cascade these like this or duplicate the defaults (which can introduce risk)
   4:     public Message()
   5:         : this(string.Empty)
   6:     {                
   7:     } 
   9:     public Message(string text)
  10:         : this(text, null)
  11:     {                
  12:     } 
  14:     public Message(string text, IDictionary<string, string> properties)
  15:         : this(text, properties, -1)
  16:     {                
  17:     } 
  19:     public Message(string text, IDictionary<string, string> properties, long timeToLive)   
  20:     {          
  21:         // ...
  22:     }
  23: } 

Now consider the same code with default parameters:
   1: public class Message
   2: {
   3:      // can either cascade these like this or duplicate the defaults (which can introduce risk)
   4:      public Message(string text = "", IDictionary<string, string> properties = null, long timeToLive = -1)
   5:      {
   6:          // ...
   7:      }
   8: } 

Much more clean and concise and no repetitive coding!  In addition, in the past if you wanted to be able to cleanly supply timeToLive and accept the default on text and properties above, you would need to either create another overload, or pass in the defaults explicitly.  With named parameters, though, we can do this easily:

1: var msg = new Message(timeToLive: 100); 

Before this specified a message with a TTL of 1000, now it specifies a message with a priority of 1000 and a time to live of -1 (infinite).  All of this with NO compiler errors or warnings.

So the rule to take away is if you are adding new default parameters to a method that’s currently in use, make sure you add them to the end of the list or create a brand new method or overload.

Pitfall: Beware of Default Parameters in Inheritance and Interface Implementation

Now, the second potential pitfalls has to do with inheritance and interface implementation.  I’ll illustrate with a puzzle:

   1: public interface ITag 
   2: {
   3:     void WriteTag(string tagName = "ITag");
   4: } 
   6: public class BaseTag : ITag 
   7: {
   8:     public virtual void WriteTag(string tagName = "BaseTag") { Console.WriteLine(tagName); }
   9: } 
  11: public class SubTag : BaseTag 
  12: {
  13:     public override void WriteTag(string tagName = "SubTag") { Console.WriteLine(tagName); }
  14: } 
  16: public static class Program 
  17: {
  18:     public static void Main() 
  19:     {
  20:         SubTag subTag = new SubTag();
  21:         BaseTag subByBaseTag = subTag;
  22:         ITag subByInterfaceTag = subTag; 
  24:         // what happens here?
  25:         subTag.WriteTag();       
  26:         subByBaseTag.WriteTag(); 
  27:         subByInterfaceTag.WriteTag(); 
  28:     }
  29: } 

What happens?  Well, even though the object in each case is SubTag whose tag is “SubTag”, you will get:
   1: SubTag
   2: BaseTag
   3: ITag 

Why?  Because default parameter are resolved at compile time, not runtime!  This means that the default does not belong to the object being called, but by the reference type it’s being called through.  Since the SubTag instance is being called through an ITag reference, it will use the default specified in ITag.

So the moral of the story here is to be very careful how you specify defaults in interfaces or inheritance hierarchies.  I would suggest avoiding repeating them, and instead concentrating on the layer of classes or interfaces you must likely expect your caller to be calling from.

For example, if you have a messaging factory that returns an IMessage which can be either an MsmqMessage or JmsMessage, it only makes since to put the defaults at the IMessage level since chances are your user will be using the interface only.

So let’s sum up.  In general, I really love default and named parameters in C# 4.0.  I think they’re a great tool to help make your code easier to read and maintain when used correctly.

On the plus side, default parameters:

Reduce redundant overloading for the sake of providing optional calling structures.
Improve readability by being able to name an ambiguous argument.

But remember to make sure you:

Do not insert new default parameters in the middle of an existing set of default parameters, this may cause unpredictable behavior that may not necessarily throw a syntax error – add to end of list or create new method.
Be extremely careful how you use default parameters in inheritance hierarchies and interfaces – choose the most appropriate level to add the defaults based on expected usage.

Technorati Tags: C#,.NET,Software,Default Parameters
Technorati Tags: .NET, C#, CSharp, Optional Parameters, C# Fundamentals
This article is part of the GWB Archives. Original Author:  James Michael Hare

Related Posts