Default Parameters vs Method Overloading

With default parameters introduced in C# 4.0 one might be tempted to abandon the old approach of providing method overloads to simulate default parameters. However, you must take in consideration that both techniques are not interchangeable since they show different behaviors in certain scenarios.

For me the most relevant difference is that default parameters are a compile time feature while method overloading is a runtime feature. To illustrate these concepts let’s take a look at a complete, although a bit long, example. What you need to retain from the example is that static method Foo uses method overloading while static method Bar uses C# 4.0 default parameters.

static void CreateCallerAssembly(string name)
{
    // Caller class - Invokes Example.Foo() and Example.Bar()
    string callerCode = String.Concat(
        "using System;",
        "public class Caller",
        "{",
        "   public void Print()",
        "   {",
        "       Console.WriteLine(Example.Foo());",
        "       Console.WriteLine(Example.Bar());",
        "   }",
        "}");

    var parameters = new CompilerParameters(new[] { "system.dll", "Common.dll" }, name);

    new CSharpCodeProvider().CompileAssemblyFromSource(parameters, callerCode);
}

static void Main()
{
    // Example class - Foo uses overloading while Bar uses C# 4.0 default parameters
    string exampleCode = String.Concat(
        "using System;",
        "public class Example",
        "{{",
        "   public static string Foo() {{ return Foo(\"{0}\"); }}",
        "   public static string Foo(string key) {{ return \"FOO-\" + key; }}",
        "   public static string Bar(string key = \"{0}\") {{ return \"BAR-\" + key; }}",
        "}}");

    var compiler = new CSharpCodeProvider();
    var parameters = new CompilerParameters(new[] { "system.dll" }, "Common.dll");

    // Build Common.dll with default value of "V1"
    compiler.CompileAssemblyFromSource(parameters, String.Format(exampleCode, "V1"));

    // Caller1 built against Common.dll that uses a default of "V1"
    CreateCallerAssembly("Caller1.dll");

    // Rebuild Common.dll with default value of "V2"
    compiler.CompileAssemblyFromSource(parameters, String.Format(exampleCode, "V2"));

    // Caller2 built against Common.dll that uses a default of "V2"
    CreateCallerAssembly("Caller2.dll");

    dynamic caller1 = Assembly.LoadFrom("Caller1.dll").CreateInstance("Caller");
    dynamic caller2 = Assembly.LoadFrom("Caller2.dll").CreateInstance("Caller");

    Console.WriteLine("Caller1.dll:");
    caller1.Print();
    Console.WriteLine("Caller2.dll:");
    caller2.Print();
}

And if you run this code you will get the following output:

// Caller1.dll:
// FOO-V2
// BAR-V1
// Caller2.dll:
// FOO-V2
// BAR-V2

You see that even though Caller1.dll runs against the current Common.dll assembly where method Bar defines a default value of “V2” the output show us the default value defined at the time Caller1.dll compiled against the first version of Common.dll. This happens because the compiler will copy the current default value to each method call, much in the same way a constant value (const keyword) is copied to a calling assembly and changes to it’s value will only be reflected if you rebuild the calling assembly again.

The use of default parameters is also discouraged by Microsoft in public API’s as stated in (CA1026: Default parameters should not be used) code analysis rule.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s