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#.
Consider this method which does not compile in VS2008 (or any other pre-4 C# and pre VS2010 environment).
1 2 3 4 5 6 7 8 |
// 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); } |
As we all know, using default values for method parameters, yields nice, simple and logical, code. Like VB.NET can do. Default arguments are a feature of CLR since “day one”. But not the feature of C#, during the 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()
.
1 2 3 4 5 6 7 8 9 10 |
// 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 ) ; } // |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
/* 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 ) ; <em>/ public Path Copy(Path destination, /</em> 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. } // |
All the code in any of the C# versions can call the method above. All the changes are encapsulated inside the method Copy()
,called. The 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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
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_); } } } // |
Please note how dbj.Implicitor
, takes care of implicit casting to the required type, at runtime. So if caller has done
1 |
Path p = Copy( path, 1, "foo" ) ; |
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