Prefer overloading than default parameters

Posted by : on

Category : C#

Introduction

Creating a C# DLL can be very helpful, if we need to have classes that we can use in different programs or to give those classes to someone else to use. By creating a library, we pack those classes in a DLL file and then by referencing this DLL, any program can use these classes as it sees fit.

As long our methods keep the same signature, they can be used even when their internal implementations change. This way, we can provide updates for our library, fixing errors and bugs, without the need for the consumer of our library to recompile his project.

The only thing that someone has to do when he uses our library in his own program and has given this program to his clients, is to provide an updated version of our DLL. Then his program will continue to work in the same way and any changes/fixes in our library will take effect, without the need for him to recompile and distribute the new binary of his code to the client.

There is a slight detail though, when we use methods with default parameters. Although his program will continue to work with our library, any changes to the values of our default parameters won’t take effect until our consumer’s program is recompiled against our new version of our library.

Methods with default parameters

In C# we can have methods that have a default value in their parameters. For example, let’s say that we have this vehicle class:

public class Vehicle
{
    readonly int _maxPassengers;
    public int MaxPassengers => _maxPassengers;

    public Vehicle(int maxPassengers = 5)
    {
        _maxPassengers = maxPassengers;
    }
}

If we create a new Vehicle without any parameters then the MaxPassengers property will return the value 5:

Vehicle vehicleWithParameter = new();
Console.WriteLine("Vehicle with parameter:" + vehicleWithParameter.MaxPassengers);
Vehicle with parameter:5

Method Overloading

We can use different code to achieve the same result as above. Instead of using a default parameter at the constructor of our Vehicle class we can overload the constructor like this:

public class Vehicle
{
    readonly int _maxPassengers;
    public int MaxPassengers => _maxPassengers;

    public Vehicle()
    {
        _maxPassengers = 5;
    }

    public Vehicle(int maxPassengers)
    {
        _maxPassengers = maxPassengers;
    }
}

this still gives us the same result.

But these two pieces of code although they seem equivalent they are not. If we see the lowered code, we can spot the difference. The code for this:

Vehicle vehicleWithParameter = new();

public class Vehicle
{
        readonly int _maxPassengers;

        public Vehicle(int maxPassengers = 5)
        {
            _maxPassengers = maxPassengers;
        }
}

gets lowered into this:

// ... lowered code before

internal class Program
{
    private static void <Main>$(string[] args)
    {
        Vehicle vehicle = new Vehicle(5);
    }
}

public class Vehicle
{
    private readonly int _maxPassengers;

    public Vehicle(int maxPassengers = 5)
    {
        _maxPassengers = maxPassengers;
    }
}

//... lowered code after

as we can see the default value is a literal constant in the caller. If we have our vehicle class in a DLL this creates a problem.

Linking to our DLL

By using the default parameter in our library, when we compile our code, our method will have the default value as a parameter.

The moment we change our library and compile it, we can give the new library to our clients, but any changes in the default parameter won’t take effect until we also recompile and distribute our own code as well. To make it more clear consider the following library:

namespace TestDefaultParameter
{
    public class Vehicle
    {
        readonly int _maxPassengers;
        public int MaxPassengers => _maxPassengers;

        public Vehicle(int maxPassengers = 5)
        {
            _maxPassengers = maxPassengers;
        }
    }
}

namespace TestOverload
{
    public class Vehicle
    {
        readonly int _maxPassengers;
        public int MaxPassengers => _maxPassengers;

        public Vehicle()
        {
            _maxPassengers = 5;
        }

        public Vehicle(int maxPassengers)
        {
            _maxPassengers = maxPassengers;
        }
    }
}

We compile that code in a DLL and then we have the following program that uses it:

using TestDefaultParameter;
using TestOverload;

TestDefaultParameter.Vehicle vehicleWithParameter = new();
TestOverload.Vehicle vehicleWithOverload = new();

Console.WriteLine("Vehicle with parameter:" + vehicleWithParameter.MaxPassengers);
Console.WriteLine("Vehicle with overload:" + vehicleWithOverload.MaxPassengers);

Console.ReadLine();

which we also compile and distribute with the DLL.

The result will be:

Vehicle with parameter:5
Vehicle with overload:5

then we need to change our library, because we decided that a better default value is 6:

namespace TestDefaultParameter
{
    public class Vehicle
    {
        readonly int _maxPassengers;
        public int MaxPassengers => _maxPassengers;

        public Vehicle(int maxPassengers = 6)
        {
            _maxPassengers = maxPassengers;
        }
    }
}

namespace TestOverload
{
    public class Vehicle
    {
        readonly int _maxPassengers;
        public int MaxPassengers => _maxPassengers;

        public Vehicle()
        {
            _maxPassengers = 6;
        }

        public Vehicle(int maxPassengers)
        {
            _maxPassengers = maxPassengers;
        }
    }
}

We compile the DLL and now our compiled program from before still can use it, because the method signature hasn’t changed, but the result will be this:

Vehicle with parameter:5
Vehicle with overload:6

when we use the default parameter in our method, our change won’t have any effect in the already compiled code. The only way to make our code see this change will be to recompile it and also redistribute it along with the new version of the DLL.

Conclusion

When we make a library for our own use, that is a problem that can be solved easily by recompiling all of our code, even if it takes some more time, but when we create a library for other people to use this can be a problem.

We will have to notify the consumers of our library, that their code needs to be recompiled and distributed again, so that can work with our new value. Depending on the situation, this can be important, as our new value may offer better performance, fix security risks or change the behaviour of our code.

Although this new version of our library will have those fixes documented, the consumers won’t be able to take advantage, unless they also recompile their code by linking it to our new version and then redistribute it again, something that can be difficult to do, either because our consumers don’t have enough resources to do or because the program that uses our library isn’t maintained anymore. In the second case, the final user will never be able to take advantage of our improvements.

Thank you for reading and as always if you have any questions or comments you can use the comments section or contact me directly via the contact form or email, also if you don’t want to miss any of the new articles, you can always subscribe to my newsletter or the RSS feed.


About Giannis Akritidis

Hi, I am Giannis Akritidis. Programmer and Unity developer.

Follow @meredoth
Follow me: