With C#, we now can have optional parameters, and give them default values like this:
public abstract class ImporterBase : IImporter {
public void ImportX(bool skipId = true) {
//....
}
}
Now, suppose in the interface we derive from, we have
public interface IImporter {
void ImportX(bool skipId = false);
}
See, that the default value is defined as different in the base class as in the interface. This is really confusing, as now the default value depends whether I do
IImporter interfaceImporter = new myConcreteImporter(); //which derives from ImporterBase
interfaceImporter.DoX(); //skipId = false
or
ImporterBase concreteImporter = new myConcreteImporter(); //which derives from ImporterBase
concreteImporter.DoX(); //skipId = true
Why is it allowed to define the default value differently in both the interface and the derived class?
Note: this question similar, but focuses on the optionality, not on the value.
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.
This is already a binary-breaking change, and this would promote it to a source-breaking change.
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.
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.