Legacy C# with default method arguments

Visual Studio 2010 is officially released. Regardless of its quality, I think that a very large percentage of companies will move from 2008 to 2010 very, very slowly. If you are a software developer “doing” C# in some of this companies, You might find this post useful.

Furthermore, there are more and more nice and useful C# samples to be found, which are in the same time presented using C#4 and VS2010. Most of that “new” code can be re-factored “back” into legacy C#. Currently, I am reluctant to invest in VS2010, so this is my solution for re-factoring C#4 methods using default arguments, back into “legacy” C#.
capture
Consider this method which does not compile in VS2008 (or any other pre-4 C# and pre VS2010 environment).

[sourcecode lang=”csharp” light=”false”] // Copies the file or folder for this path to another location.
/// <param name="destination">The destination path.</param>
/// <param name="overwrite">True if the destination can be overwritten. Default is false.</param>
/// <param name="recursive">True if the copy should be deep and include subdirectories recursively. Default is false.</param>
// returns The source path
public Path Copy(Path destination, overwrite = false, recursive = false ) {
return Copy(destination.ToString(), overwrite, recursive);
}
[/sourcecode]

As we all know, using default values for methods parameters, yields nice, simple and logical, code. Like VB.NET can do. Default arguments are feature of CLR since the “day one”. But not the feature of C#, during last 9+ years (Wow, that is a long time to be using C# ?! ). To do the above using pre-4 C#, one needs to resort to overloads. To match any of the possible variations of the client code, calling the method Copy().

[sourcecode lang=”csharp” light=”false”] // Compiles in all versions of C#
void copy_calling_variants ( Path path ) {
// parameters overwrite and recursive are false by default
Path p = Copy( path ) ;
// overwrite is true , false is default
p = Copy( path, true ) ;
// both overwrite and recursive are true, by callers wish
p = Copy( path, true, true ) ;
}
//
[/sourcecode]

On top of the code above C#4 can use named arguments. Which are outside of the scope of my solution, which I have designed to kind-of-a “implement” default values of method parameters.

Without further obscuring the issues, here is the code of the method Copy(), that is using my solution:

[sourcecode lang=”csharp” light=”false”] /*
Any C# variant can call this method on any of these three ways:
// parameters overwrite and recursive are false by default
Copy( path ) ;
// overwrite is true , false is default
Copy( path, true ) ;
// both overwrite and recursive are true, by callers wish
Copy( path, true, true ) ;
*/
public Path Copy(Path destination,
/* have to use object [] , to pass
overwrite = false, recursive = false
*/
params object [] argz )
{
// argz iz what the caller has given
dbj.IParams p = dbj.make.paramz(argz,
// first optional argument is by default false, and we will call it ‘overwrite’
"overwrite", false,
// second optional argument is by default false, and we will call it ‘recursive’
"recursive", false
);
// take the value of the first optional argument,
// use false as default value if not caller defined
// take the value of the second optional argument,
// use false as default value if not caller defined
return Copy(destination.ToString(),
p["overwrite"],
p["recursive"] );
//NOTE: inner Copy() expects types of 2-nd and 3-rd parameter to be boolean.
//Due to this fact compiler will generate implicit casting of
// p["overwrite"] and p["recursive"] into boolean types.
//But, the actual casting will happen at run-time,
// when caller gives values to optional
// parameters above.
}
//
[/sourcecode]

All the code in any of the C# versions can call the method above. All the changes are encapsulated inside the method Copy(),called. Core of the solution is to use the params object [] feature. Plus two of the classes I developed to simplify the usage of params, and thus make the refactoring as simpler as possible.

The whole of the implementation is packaged inside the namespace dbj { }, in the separate assembly. It is used through one interface and one class , implemented in there.

[sourcecode lang=”csharp” light=”false”] namespace dbj
{
using System;
/// <summary>
/// Consider dictionary of name/value pairs, and an array of objects.
/// Use the dictionary, to determine for an array of objects, what are
/// the default values for missing elements
/// and what is the (string) name of each element.
/// Implementations of this interface are to be used for handling
/// params object [] , argument of a method.
/// default names and values are to be provided to the public contructor
/// as optional arguments.
/// </summary>
/// <remarks>
/// NOTE: please understand the difference between named and optional arguments.
/// For optional arguments position in the params object [], is important.
/// And names are internal only.
/// </remarks>
public interface IParams
{
/// <summary>
/// return the given or default value for the named argument
/// result is wrapped in Iconvertible type and thus is
/// implicitly convertible to the nesting expression or the
/// lvalue of the assignment where this property is the rvalue.
/// See also: <seealso cref="dbj.Implicitor"/>
/// </summary>
/// <param name="argument_name">argument name</param>
/// <returns>instance of the Iconvertible implementation</returns>
dbj.Implicitor this[string argument_name] { get; }
}
// The only front end to the library is one factory method.
public sealed class make
{
/// <summary>
/// Create instance of the class that implements IParams interface.
/// </summary>
/// <param name="parz_">params object [], of the method from which called.</param>
/// <param name="defs_">optional list of name,value pairs</param>
/// <returns></returns>
static public dbj.IParams paramz(object[] parz_, params object[] defs_)
{
return new dbj.Paramz(parz_, defs_);
}
}
}
//
[/sourcecode]

Please note how dbj.Implicitor, takes care of implicit casting to the required type, at runtime. So if caller has done

[sourcecode lang=”csharp” light=”false”] Path p = Copy( path, 1, "foo" ) ;
[/sourcecode]

An exception will be thrown at runtime, since internally bool optional arguments were expected, and arguments 1 and “foo” can not be cast to booleans.

My fork of “Fluent Path” by Bertrand Le Roy has this implemented, working, tried and tested. It resides here : https://hg01.codeplex.com/forks/dbjdbj/fp08 .

Also please see Bertand’s post on how VS2010 actually handles optional arguments, if one wants to compile for .NET 3.5 or older, using VS2010.

–DBJ