Why Are C# 4 Optional Parameters Defined on Interface Not Enforced on Implementing Class

Why are C# 4 optional parameters defined on interface not enforced on implementing class?

UPDATE: This question was the subject of my blog on May 12th 2011. Thanks for the great question!

Suppose you have an interface as you describe, and a hundred classes that implement it. Then you decide to make one of the parameters of one of the interface's methods optional. Are you suggesting that the right thing to do is for the compiler to force the developer to find every implementation of that interface method, and make the parameter optional as well?

Suppose we did that. Now suppose the developer did not have the source code for the implementation:


// in metadata:
public class B
{
public void TestMethod(bool b) {}
}

// in source code
interface MyInterface
{
void TestMethod(bool b = false);
}
class D : B, MyInterface {}
// Legal because D's base class has a public method
// that implements the interface method

How is the author of D supposed to make this work? Are they required in your world to call up the author of B on the phone and ask them to please ship them a new version of B that makes the method have an optional parameter?

That's not going to fly. What if two people call up the author of B, and one of them wants the default to be true and one of them wants it to be false? What if the author of B simply refuses to play along?

Perhaps in that case they would be required to say:

class D : B, MyInterface 
{
public new void TestMethod(bool b = false)
{
base.TestMethod(b);
}
}

The proposed feature seems to add a lot of inconvenience for the programmer with no corresponding increase in representative power. What's the compelling benefit of this feature which justifies the increased cost to the user?


UPDATE: In the comments below, supercat suggests a language feature that would genuinely add power to the language and enable some scenarios similar to the one described in this question. FYI, that feature -- default implementations of methods in interfaces -- will be added to C# 8.

C# optional parameters: Why can I define the default different on interface and derived class?

To clarify, I'm interpreting the question to be:

If a method is defined in an interface / base class which has a method which has a parameter with a default value, and a class implements / overrides that method but provides a different default value, why doesn't the compiler warn?

Note that this doesn't cover the case where the implementation doesn't provide any default value -- that case is explained by Eric Lippert.


I asked this on the csharplang gitter channel, and the response from someone who has been heavily involved in the language design for a long time was:

i think an analyzer sounds very good for this.

From that, and the other links posted here (which don't even mention this specific case), my best guess is that this specific case just wasn't considered, or was briefly considered but dismissed as too niche. Of course, once C# 4 was released, there was no way to add a compiler error or warning without breaking backwards compatibility.

You could write an analyzer which catches this case (which had a code fix to correct the default value), and try to get it incorporated into Roslyn.


As a footnote, there are a few cases I can see which would cause issues.

An interface changes the default value for one of its parameters

This is already a binary-breaking change, and this would promote it to a source-breaking change.

Two interfaces with different default values

interface I1
{
void Foo(bool x = false);
}
interface I2
{
void Foo(bool x = true);
}
class C : I1, I2
{
...?
}

If you did want to specify a default value for C.Foo, this case could be solved by explicitly implementing one of the interfaces:

class C : I1, I2
{
public void Foo(bool x = false) { ... }
void I2.Foo(bool x) => Foo(x);
}

Alternatively you could just ignore this case, and not warn.

Adding an interface in a child class

interface I1
{
void Foo(bool x = false);
}
class Parent
{
public void Foo(bool x = true) { ... }
}
class Child : Parent, I1
{
...?
}

I'm not sure what an intuitive solution to this would be, but since it's so niche I'd be tempted just to ignore it, and not warn.

Interface implementation with optional arguments

Because optional arguments in C# are just syntactic sugar.

The method definition in your case is

void Store(string payload, bool swallowException)

rather than

void Store(string payload)

Which obviously doesn't match the interface.

The way default arguments work is that the compiler injects the default values into the call of the method. So if you do Store(payload), the compiler will actually emit Store(payload, true). This is extremely important for understanding of default arguments - it's done in compile time of the caller. So if you change the default argument in the callee without recompiling the caller, the caller is still going to use the old default argument.

This also explains the warning you got - since the default value is passed by the compiler explicitly, and you can't call an explicit implementation of an interface without casting to the interface, you're not going to get an opportunity to use the default value, ever.

You don't actually want to use default arguments at all. Simply define two methods like this:

void Store(string payload, bool swallowException)
{
// Do your job
}

void Store(string payload)
{
Store(payload, true);
}

This avoids both of the problems above - the interface contract is satisfied, and the default argument is now part of the callee, not the caller.

Personally, I don't use optional arguments in public API methods at all - they're just aching to cause trouble when you decide that you want to change them at some point. Unless you can make sure they will stay the same forever, don't use them. The same applies to const and enum - both are also determined at compile-time, rather than run-time.

Remember, the reasoning for including default arguments is to allow you to not pass some argument. That makes sense for things like COM API calls (which would otherwise require you to pass all the arguments you don't want to pass as Type.Missing), or null values. Even using false is just asking for trouble when someone decides that a better default would be true - suddenly, some callers are using true and some false, although all think they're using the "default". For a case like yours, I'd use bool? instead, with a default value of null (or default(bool?), whichever you prefer). In the method code itself, you can then easily handle the default at the proper point - say, by doing swallowException.GetValueOrDefault(true).

Is there any reason to declare optional parameters in an interface?

Example:

public interface IService1
{
void MyMethod(string text, bool flag = true);
}

public class MyService1a : IService1
{
public void MyMethod(string text, bool flag) { }
}

Usage:

IService1 ser = new MyService1a();
ser.MyMethod("A");

2nd parameter passed to MyService1a will be true, as default parameter in interface.

Optional parameters for interfaces

You could consider the pre-optional-parameters alternative:

public interface IFoo
{
void Bar(int i, int j);
}

public static class FooOptionalExtensions
{
public static void Bar(this IFoo foo, int i)
{
foo.Bar(i, 0);
}
}

If you don't like the look of a new language feature, you don't have to use it.

Optional arguments in interface without any default value

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).

Warning From Explicitly Implementing an Interface with Optional Parameters

The problem with optional arguments in C# is whether the callee sees the object as a TestClass or an ITestInterface. In the first case, the values declared in the class apply. In the second case the values declared in the interface apply. It is because the compiler uses the statically available type information to construct the call. In case of an explicit interface implementation the method is never called 'for a class', always 'for an interface'

The C# Language Specification in 10.6.1 states:

If optional parameters occur in an implementing partial method declaration (§10.2.7) , an explicit interface member implementation (§13.4.1) or in a single-parameter indexer declaration (§10.9) the compiler should give a warning, since these members can never be invoked in a way that permits arguments to be omitted.

Can an optional parameter be an empty class object type and NOT null?

I would use a method overload:

public abstract class Action
{
public string Name { get; private set; }

public Action(string name) { Name = name; }

public bool PerformAction() => PerformAction(ActionArgs.Empty);

public abstract bool PerformAction(ActionArgs args);
}

Backward compatible when using three optional parameters vs one optional parameters

It depends on whether you care about code compatibility vs binary compatibility; the latter is usually mandatory (to not break compiled code), but the former can be a little more flexible.

You can't just add the new overload, because then Test("abc") is ambiguous and no longer compiles.

Edit: as canton7 notes: you could add a Test(string a) method to avoid that ambiguity, but: others remain (see below), and it further complicates the API surface.

If we only care about binary compatability, we can do:

public void Test(string a, string b, int? c, string d)
=> Test(a, new TestOptions { ... }); // forwarded
public void Test(string a, TestOptions options = null) { ... }

But this will break code compatibility, because the existing code Test("abc", "def") no longer compiles. We can't just make the second parameter mandatory, because if we do that, the existing code Test("abc", c: 42) no longer compiles.


If you care about the best code compatibility, honestly the best option is to not make options optional. For example:

public void Test(string a, string b, int? c, string d)
=> Test(a, new TestOptions { ... }); // forwarded
public void Test(string a, TestOptions options) { ... }

This solves almost all problems of code compatibility. There's still one edge case: Test("abc", null) - so: how much do you care about that?



Related Topics



Leave a reply



Submit