7
votes

I was surprised that C# uses a value for optional method argument of interface, not from class implementing this interface. For example:

using System;
                
public class Program
{
    private static IMyInterface instance;
    
    public static void Main()
    {
        instance = new MyClass();
        
        instance.PrintOpt();
        ((MyClass)instance).PrintOpt();
    }
}

public interface IMyInterface
{
    void PrintOpt(bool opt = false);
}

public class MyClass : IMyInterface
{
    public void PrintOpt(bool opt = true) 
    {
        Console.WriteLine($"Value of optional argument is {opt}");
    }
}

produces output:

Value of optional argument is False

Value of optional argument is True

My question is: is it possible to define an optional parameter in an interface without default value or "overridable", so calling of method on instance saved in variable of interface type uses a optional value defined in a class implementing this interface?

1
Why is it so surprising? You typed your instance var as IMyInterface - why would you expect it to use anything other than the interface's definition?BoltClock♦
Good related explanation in this SO answer from Eric LippertSteven Rands
I'm surprised that the default value isn't a part of the method signature. In other words... since the interface specifies a method with bool param defaulted to FALSE, I'm surprised that the class isn't required to implement the method with bool param defaulted to FALSE.Joe

1 Answers

10
votes

This wouldn't be surprising if you understood how optional arguments are handled internally: they are inlined during compilation.

In other words, at the place where a method is called, any optional arguments are passed by the compiler - if you're calling an interface method, the compiler has no idea that there's an implementation with a different optional argument. The difference is best seen with code like this:

IMyInterface interface = new MyClass();
MyClass theClass = (MyClass)interface;

interface.PrintOpt(); // false
theClass.PrintOpt(); // true

Which is compiled to this (translated back to C#):

interface.PrintOpt(false);
theClass.PrintOpt(true);

The "default" arguments are no longer "default" in the IL code - they are just another explicitly passed argument.

If you want to use optional arguments that are overridable, just use method overloads. Or better, use default values that don't mean anything (e.g. null or default(int?)) and do any replacing for defaults inside of the method. This is in line with the original reason for including optional arguments to C# in the first place - VB-style COM interfaces often have methods with dozens of arguments, almost all of which are optional. Until now, when you wanted to call a method like this, you had to do something like

comInterface.MyMethod(TheActualArgumentICareAbout, Type.Missing, Type.Missing, 
                      Type.Missing, Type.Missing, ...);

Now you can just do

comInterface.MyMethod(argument, anotherSuperUseful: true);

This distinction is a big deal - it also means that you shouldn't ever change default arguments on any public method. Anyone using your library without recompiling will still use the old default value. It's similar to how const values or enums are handled. If you use a null for a default instead, the actual default value will be inside the method itself, and all callers will "see" the correct default even if they don't recompile (similar to using a public static readonly field instead of const, or a class with properties instead of an enum).