Use Optional Method Parameters to Keep Method Signatures Backward Compatible

From C# 4.0, methods, constructors, and indexers can declare optional parameters. A parameter is optional if it specifies a default value:

void Foo (int x = 24) {Console.WriteLine(x) }

Optional parameters can be omitted when calling the method:

Foo(); // 24

The default argument of 24 is actually passed to the optional parameter x - the compiler bakes the value into the compiled code at the calling side - it substitutes the default value of an optional parameter whenever it is used.

The default value of an optional parameter must be specified by a constant expression or a parameterless constructor of a value type. Optional parameters cannot be marked with ref or out.

Mandatory parameters must occur before optional parameters in both the method declaration and the method call, the exception being params arguments, which must always come last.

What this does is allow us to modify the effective signature and business logic of a method by adding one or more optional parameters, without having to change any legacy code calling the method. Here's a short example to illustrate.

Say I have a method GetOpenConnection() that is called from many places in my code. It delivers a connection based on an element in the connectionStrings section of the configuration file. The method originally had an overload where you can specify the connection string name:

GetOpenConnection("connection1").

But let's say now that in one application that I intend to distribute, I don't want users poking around in my config file getting my connection info, so I have decided to bake my connection string into the code as a static property. (Nevermind that I can encrypt the connectionStrings section - in this case I don't even want to have a configuration file).

Here's the sample Stuff class that holds my connection string (and whatever else I might want in there):

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data.SqlClient;
using System.Linq;
using System.Reflection;
using System.Text;

namespace StaticProperties
{
   public static  class Stuff
    {
       public static string ConnectionString
       {
           get { return "server=127.0.0.1;database=Northwind;Integrated Security=SSPI"; }
       }

       public static SqlConnection GetOpenConnection(string name, bool isMetaData = false)
       {
           string connectionString = String.Empty;
           if (!isMetaData) // if default, get from config file
               connectionString = ConfigurationManager.ConnectionStrings[name].ConnectionString;
           else // not default, get from static class
               connectionString = Stuff.ConnectionString;

           SqlConnection conn = new SqlConnection(connectionString);
           return conn;
       }

     public  static string GetPropertyValue(Type myType, string propertyName)
       {
           PropertyInfo[] properties = myType.GetProperties(BindingFlags.Public | BindingFlags.Static);
           foreach (PropertyInfo property in properties)
           {
               if (property.Name == propertyName)
               {
                   return (string)property.GetValue(myType, null);
                   break;
               }
           }
           return null;
       }

    }
  
You can see that I have a readonly ConnectionString property, a GetOpenConnection method with a string name and optional isMetaData property, and a utility GetPropertyValue method that allows the user to just get the value of a named string property from a type.

Here's the calling code:

using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;
using System.Reflection;
using System.Text;

namespace StaticProperties
{
    class Program
    {
         static void Main(string[] args)
        {
            string conn = Stuff.GetPropertyValue(typeof (Stuff), "ConnectionString");
           Console.WriteLine("From GetPropertyValue:\r\n " +conn);

             using (SqlConnection connection= Stuff.GetOpenConnection("config"))
            {
               Console.WriteLine(" From Config (name only):\r\n " +connection.ConnectionString);
             }

             using (SqlConnection connection2 = Stuff.GetOpenConnection("config", true))
            {
               Console.WriteLine("From Stuff (IsMetaData=true):\r\n "+ connection2.ConnectionString);
            }
           Console.ReadLine();
        }
    }
}


First, we show getting the string using the GetPropertyValue method. Next, we show using the name of a config element. Since we didn't specify a value for the optional isMetaData parameter, its value will be false (the default) and the method will return the connection specified in the config file.

But now, I have no configuration file. So in the third method call, I specify "true" for the optional parameter, and the business logic of the method says "Oh- I'm supposed to get this guy from my own property value in this class" - and so it ignores the name parameter and does it's thing.

The bottom line is that all the hundreds of method calls that don't use this feature do not need to be changed at all.

You can download the sample Visual Studio 2010 Console Application here.

By Peter Bromberg   Popularity  (5457 Views)