5

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.

Marcel
  • 15,039
  • 20
  • 92
  • 150
  • 1
    `ImporterBase, IImporter` should be `ImporterBase : IImporter` I guess – Michał Turczyn Jan 09 '20 at 13:50
  • Because it's `optional` the compiler doesn't enforce that default value, it only cares the signatures match (not the value itself); a bool is a bool, doesn't matter if true or false. On another note [Eric Lippert](https://stackoverflow.com/a/4923642/1797425) talks about this exact issue (by design). – Trevor Jan 09 '20 at 13:58
  • Substitute "change the optional value" for "make a parameter optional" in the accepted answer on that other question and does that not point out a similar issue with your proposal? That you're not guaranteed that interfaces and classes are compiled in the "right" order? – Damien_The_Unbeliever Jan 09 '20 at 13:58
  • @MichałTurczyn Yes, I really should be more careful. :-/ – Marcel Jan 09 '20 at 13:58
  • @Damien_The_Unbeliever I don't think so. The other question muses about having the parameter optional or not, but I wonder why it can have a different default value. I would have guessed, that once in the derivation chain, the default is set, no downstream implementation may define the default to a different value than the one already given upstreams. – Marcel Jan 09 '20 at 14:03
  • But what if the interface then changes the value and *it* is recompiled but the classes aren't? Exact same type of issue. – Damien_The_Unbeliever Jan 09 '20 at 14:06

2 Answers2

2

There's a good reason for it. See here.

The short answer is though, if it treated the optional values as part of the method signature, it cause some problems. Imagine the code below:

public abstract class A
    {
        public abstract void SomeFunction(bool flag);
    }

    public class B : A
    {
        public override void SomeFunction(bool flag = true)
        {
            //do something
            Console.WriteLine(flag);
        }
    }

If the optional parameter value was part of the method signature then I'd get a compilation error since A doesn't have bool flag = true in the method signature. It's an easy fix for sure but if you shipped A to a third party and their custom code created class B, they'd have to go change your code to have the optional parameter. Also keep in mind this is exacerbated when there are several levels of inheritance. So the easiest fix was to not consider the optional parameter value as part of the method signature for these purposes.

chris-crush-code
  • 1,114
  • 2
  • 8
  • 17
  • 1
    I think the question asks why the two methods are allowed to have *different* default values. So `A` would have `bool flag = false`, not `bool flag`. – canton7 Jan 09 '20 at 14:07
  • 1
    They're allowed to because the value is not part of the method signature. bool flag = false, bool flag = true, and bool flag are all treated the same because only the parameter name and type are parts of the method signature. – chris-crush-code Jan 09 '20 at 14:12
  • Hypothetically the compiler *could* raise an error/warning if it sees that both the base method and override have a default parameter, but the defaults are different. The question asks why the compiler does not raise an error/warning here. Nothing to do with defaults being part of the method signature. – canton7 Jan 09 '20 at 14:14
  • 1
    Because the compiler just sees it as SomeFunction(bool), no? – Alfie J. Palmer Jan 09 '20 at 14:14
  • 1
    The *compiler* knows that `B.SomeFunction` overrides `A.SomeFunction`, and it knows the full signatures of both (including any default values for parameters). It could easily raise an error/warning here. The question is: why doesn't it. – canton7 Jan 09 '20 at 14:16
  • 1
    the reason why it doesn't raise an error is my answer - to make the error go away you'd have to go back and update the default value in the entire inheritance chain so MS decided to allow the functionality as a work around. The link provided explains it pretty well. – chris-crush-code Jan 09 '20 at 15:06
  • Not really. The Eric addresses two interfaces with different default values (which is valid), but dismisses it with "*that's a rare case and we can probably dismiss it as unlikely*". Everything else is either 1) forcing an implementation to repeat the default value from an interface, or 2) issues with an interface adding a default value. Neither of these apply to this question. The only issue I can think of which would affect this question is an interface *changing* its default value, which seems unlikely – canton7 Jan 09 '20 at 15:18
  • 1
    As I read it, the question is specifically about "interface gives one default value, implementation gives a different one", and why that doesn't raise an error. It is *not* about the case where an interface gives a default value but an implementation does not give one at all. – canton7 Jan 09 '20 at 15:20
2

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.

canton7
  • 37,633
  • 3
  • 64
  • 77
  • Your Interpretation in the first paragraph is right. Moreover, instead of warning, I expected it even to fail. – Marcel Jan 10 '20 at 10:17
  • 1
    @Marcel I think it's fair to say that it doesn't fail because it doesn't *need* to fail: there's nothing which stops the compiler from compiling this perfectly fine (as explained elsewhere). It might however indicate a developer mistake, which is the realm of warnings. However if written as an analyzer, projects would be able to choose whether they wanted to error/warning/message/etc. – canton7 Jan 10 '20 at 10:19
  • Great explanation and answer, I wrote a quick analyzer in a few minutes that enforces this, only option viable I see. – Trevor Jan 10 '20 at 15:31