Dot Net Perls

Overload Methods - C#

by Sam Allen

Problem

Overload methods for improved code clarity and simpler logic. Often when designing complex programs, code will be changed and some branches will become unnecessary. Use overloaded methods to eliminate complexity and enhance performance.

Solution: C#

If you read one sentence here, read this one: Overloaded methods are completely separate from each other. A method receiving a string parameter is separate from one receiving no parameter or an int.

//
// These two methods are entirely separate
//
void MethodA()
{
    // etc.
}
void MethodA(string a)
{
    // etc.
}

What it's good for. So, why bother? Well, here I am not focusing on polymorphism and inheritance, but rather a simpler way of improving code readability and performance. Here's some code that is problematic.

class ExampleA
{
    public ExampleA()
    {
        //
        // A.
        // Here we know we want to show the string 'Popular'
        // since the category is None
        //
        ShowString(string.Empty);

        //
        // B.
        // Here we want to show the category name
        //
        ShowString("Category");
    }

    static void ShowString(string value)
    {
        if (value == string.Empty)
        {
            Console.WriteLine("Popular");
        }
        else
        {
            Console.WriteLine(value);
        }
    }
}

What are we fixing?

The code above has an unnecessary branch, but this cannot be optimized out by the compiler in the IL code. Look at part B above. It calls into ShowString, but we know that the string is never empty at that call site.

Remove branches. When optimizing C, I learned that removing branches greatly increases performance. Processors use branch prediction, but this is not ideal. You want your code to go sequentially from beginning to end.

How to fix it. We will use an overloaded method to fix this problem. You can actually name the methods anything you want, but this way is most elegant and easy to maintain years later.

//
// Contains these methods:
// 1. ShowString()
// 2. ShowString(string)
//
class OverloadA
{
    public OverloadA()
    {
        //
        // A.
        // We know we want to show 'Popular' and not
        // the category name.
        //
        ShowString();

        //
        // B.
        // Here we want to show the category name
        //
        ShowString("Category");
    }

    static void ShowString()
    {
        //
        // Send default argument to overload
        //
        ShowString("Popular");
    }

    static void ShowString(string value)
    {
        //
        // We don't need an if check here, which makes
        // calling this method directly faster
        //
        Console.WriteLine(value);
    }
}

How does it work?

What I did was look into the IL (intermediate language) to see exactly what the overloaded methods become when first compiled. First, let's look inside the ShowString method that has the if statement.

// static void ShowString(string value)
// {
//     if (value == string.Empty)
//     {
//         Console.WriteLine("Popular");
//     }
//     else
//     {
//         Console.WriteLine(value);
//     }
// }

.method private hidebysig static void  ShowString(string 'value') cil managed
{
  // Code size       31 (0x1f)
  .maxstack  8
  IL_0000:  ldarg.0
  IL_0001:  ldsfld     string [mscorlib]System.String::Empty
  IL_0006:  call       bool [mscorlib]System.String::op_Equality(string,
                                                                 string)
  IL_000b:  brfalse.s  IL_0018
  IL_000d:  ldstr      "Popular"
  IL_0012:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_0017:  ret
  IL_0018:  ldarg.0
  IL_0019:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_001e:  ret
} // end of method ExampleA::ShowString

What we see. The if chain in ShowString uses an op_Equality. None of this is compiled out and it probably couldn't be compiled out. Therefore, ShowString will always have the branch.

Overloaded calls. Next I show how the overloaded methods are called in the second example. The methods are completely separate. The compiler can easily tell the difference between overloads with different parameters.

// public OverloadA()
// {
//    ShowString();
//
//    ShowString("Category");
// }

.method public hidebysig specialname rtspecialname
        instance void  .ctor() cil managed
{
  // Code size       22 (0x16)
  .maxstack  8
  IL_0000:  ldarg.0
  IL_0001:  call       instance void [mscorlib]System.Object::.ctor()
  IL_0006:  call       void OverloadA::ShowString()
  IL_000b:  ldstr      "Category"
  IL_0010:  call       void OverloadA::ShowString(string)
  IL_0015:  ret
} // end of method OverloadA::.ctor

Streamlined methods. Finally, the ShowString() method doesn't need any if-else chain at all. It simply does what it is told, without any checking at all. Your CPU will like that.

.method private hidebysig static void  ShowString() cil managed
{
  // Code size       11 (0xb)
  .maxstack  8
  IL_0000:  ldstr      "Popular"
  IL_0005:  call       void OverloadA::ShowString(string)
  IL_000a:  ret
} // end of method OverloadA::ShowString

Discussion

Refactor your code to use method overloading when possible. The overloads are easily inferred by the compiler. You can streamline old code that contains unecessary if-else chains. This way you can eliminate run-time inefficiencies by more clearly expressing the logic to the compiler.

Dot Net Perls
About
Sitemap
Source code
RSS
Language Features
Struct Examples and Tricks
Run Commands With Process.Start
Singleton Pattern vs. Static Class
NGEN Installer Class
Enum Tips and Examples
Recent
Pi
NGEN Installer Class
List Element Equality
DateTime Tips and Tricks
Remove HTML Tags From String
© 2008 Sam Allen. All rights reserved.