C#可选参数:为什么我可以在接口和派生上定义不同的默认值class?

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

使用 C#,我们现在可以有 可选参数,并为它们提供 默认 值,如下所示:

public abstract class ImporterBase : IImporter {
    public void ImportX(bool skipId = true) {
        //....
    }
}

现在,假设在我们派生的接口中,我们有

public interface IImporter {
    void ImportX(bool skipId = false);
}

请注意,默认值在基础 class 中的定义与界面中的不同。这真的很令人困惑,因为现在默认值取决于我是否这样做

IImporter interfaceImporter = new myConcreteImporter(); //which derives from ImporterBase
interfaceImporter.DoX(); //skipId = false

ImporterBase concreteImporter = new myConcreteImporter(); //which derives from ImporterBase
concreteImporter.DoX(); //skipId = true

为什么允许在接口和派生中定义不同的默认值class?

注:this question similar, but focuses on the optionality, not on the value.

这是有充分理由的。参见 here

不过,简短的回答是,如果将可选值视为方法签名的一部分,则会导致一些问题。想象一下下面的代码:

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);
        }
    }

如果可选参数值是方法签名的一部分,那么我会收到编译错误,因为 A 在方法签名中没有 bool flag = true。这肯定是一个简单的修复,但如果您将 A 发送给第三方并且他们的自定义代码创建了 class B,他们将不得不更改您的代码以具有可选参数。还要记住,当有多个继承级别时,这种情况会加剧。因此,最简单的解决方法是不要将可选参数值视为用于这些目的的方法签名的一部分。

为了澄清,我将问题解释为:

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?

请注意,这不包括实现不提供 任何 默认值的情况——这种情况由 Eric Lippert.[=18 解释=]


我在 csharplang gitter channel 上问过这个问题,长期从事语言设计的人的回答是:

i think an analyzer sounds very good for this.

据此,以及此处发布的其他链接(甚至没有提到这个具体案例),我最好的猜测是这个具体案例没有被考虑过,或者被简单地考虑过但因为过于小众而被驳回。当然,一旦 C# 4 发布,就无法在不破坏向后兼容性的情况下添加编译器错误或警告。

您可以编写一个分析器来捕获这种情况(它有一个代码修复来更正默认值),并尝试将其合并到 Roslyn 中。


作为脚注,我可以看到一些会导致问题的情况。

接口更改其参数之一的默认值

这已经是一个破坏二进制文件的更改,这将把它提升为一个破坏源代码的更改。

两个默认值不同的接口

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

如果您确实想为 C.Foo 指定一个默认值,这种情况可以通过显式实现接口之一来解决:

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

或者您可以忽略这种情况,而不发出警告。

在子项中添加接口 class

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

我不确定这个问题的直观解决方案是什么,但由于它太小众了,我很想忽略它,而不是发出警告。