ChatGPT解决这个技术问题 Extra ChatGPT

为什么在接口上定义的 C# 4 可选参数未在实现类上强制执行?

我注意到使用 C# 4 中的可选参数,如果您在接口上指定可选参数,则不必在任何实现类上将该参数设为可选:

public interface MyInterface
{
    void TestMethod(bool flag = false);
}

public class MyClass : MyInterface
{
    public void TestMethod(bool flag)
    {
        Console.WriteLine(flag);
    }
}

因此:

var obj = new MyClass();        
obj.TestMethod(); // compiler error

var obj2 = new MyClass() as MyInterface;
obj2.TestMethod(); // prints false

有谁知道为什么可选参数被设计成这样工作?

一方面,我认为覆盖接口上指定的任何默认值的能力很有用,但老实说,我不确定您是否甚至应该能够在接口上指定默认值,因为这应该是一个实现决定。

另一方面,这种断开连接意味着您不能总是交替使用具体类和接口。当然,如果在实现上指定了默认值,这当然不是问题,但是如果您将具体类公开为接口(例如,使用一些 IOC 框架注入具体类),那么真的没有点具有默认值,因为调用者无论如何都必须始终提供它。

因为它们是可选的?
但是您可以将对象实例转换为 MyInterface 并使用可选参数调用它:((MyInterface)obj).TestMethod();
@oded - 但是如果你说这个参数在合同上是可选的,为什么你允许实施者不让它成为可选的?这不会让任何想要使用合同的人感到困惑吗?
我认为在这种情况下,您可以说参数在实现时是可选的,而不是在调用实现方法时。当您在类中调用方法时,您必须遵循类规则(参数在类中不是可选的,因此您可以'不调用没有它的方法),另一方面,当你实现接口时,你必须遵循接口规则,所以你可以覆盖带有/不带可选参数的方法。只是一个意见。

E
Eric Lippert

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

假设你有一个你描述的接口,以及一百个实现它的类。然后,您决定将接口方法之一的参数之一设为可选。您是否建议正确的做法是让编译器强制开发人员找到该接口方法的每个实现,并使参数也可选?

假设我们这样做了。现在假设开发人员没有实现的源代码:

// 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

D的作者应该如何完成这项工作?你的世界是否需要他们打电话给 B 的作者并要求他们向他们发送一个新版本的 B,使该方法具有可选参数?

那不会飞的。如果两个人打电话给 B 的作者,其中一个希望默认为真,而其中一个希望默认为假怎么办?如果 B 的作者只是拒绝配合怎么办?

也许在那种情况下,他们会被要求说:

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

提议的特性似乎给程序员增加了很多不便,但代表权并没有相应增加。这个特性有什么引人注目的好处,证明增加的用户成本是合理的?

更新:在下面的评论中,supercat 建议了一种语言功能,该功能可以真正增加语言的功能,并启用一些类似于此问题中描述的场景。仅供参考,该功能(接口中方法的默认实现)将添加到 C# 8。


@supercat:我们没有任何计划这样做,但这样的功能是可能的。以前有人提出过。在 C# 4 的设计过程中,我们使用了很多我们称之为“扩展一切”的特性——我们有扩展方法,那么扩展事件、扩展属性、扩展构造函数、扩展接口等等意味着什么.您可以“附加”到接口并说“这些方法是此接口的默认实现”的类是表征“扩展接口”的一种可能方式。一个有趣的想法。
澄清我为什么认为它不直观的原因:对我来说,可选参数是“方法调用者可选”而不是“接口实现者可选”。
“你们的世界是否需要他们打电话给 B 的作者并要求他们给他们发一个新版本[...]?”不,不需要电话。 B 应该不再被认为是 MyInterface 的实现,并且 D 的作者可以通过编译器意识到这一点。 D 的作者将需要使用接受默认参数的 TestMethod 来实现 D,因为这是接口作者所要求的 - 您反对允许接口作者强制执行约束,因为有人可能想要破坏它。这不是一个好的论据。
@EricLippert在为方便编码而指定可选参数的情况下,是的,在实现中强制执行不便是违反直觉的。但是在使用可选参数来最小化方法重载(一个强大的功能)的情况下,这些可选值可能具有重要意义,忽略它们肯定会削弱这种能力。更不用说具体实现指定与接口不同的默认值的情况。这似乎是一个非常奇怪的断开连接。
我在这里同意@philofinfinitejest。界面告诉我们可以做什么。如果我传递了一个接口的实现,其默认值与接口指定的值不同,我怎么知道呢?嘿,我有一个默认传递 true 的接口,那么为什么这个东西会变成 false 呢?那,似乎我没有得到我期望的界面。更糟糕的是,我现在必须针对实现而不是接口进行编程。
C
CodesInChaos

可选参数只是用属性标记。该属性告诉编译器在调用点插入该参数的默认值。

当 C# 代码编译为 IL 时,调用 obj2.TestMethod(); 将替换为 obj2.TestMethod(false);,而不是在 JIT 时间。

所以在某种程度上它总是调用者提供带有可选参数的默认值。这也对二进制版本控制产生影响:如果您更改默认值但不重新编译调用代码,它将继续使用旧的默认值。

另一方面,这种断开连接意味着您不能总是交替使用具体类和接口。

如果接口方法是 implemented explicitly,您已经不能这样做了。


你能强制实施者明确实施吗?
O
Olhovsky

因为默认参数是在编译时解析的,而不是运行时解析的。所以默认值不属于被调用的对象,而是属于被调用的引用类型。


A
Ariel Arjona

据我了解,可选参数有点像宏替换。从方法的角度来看,它们并不是真正可选的。其中的一个产物是,如果你转换到一个接口,你会看到你得到不同结果的行为。


H
Harshdeep Singh

只是想在这里添加我的看法,因为其他答案确实提供了合理的解释,但不是完全让我满意的解释。

可选参数是在调用站点编译时注入默认值的语法糖。这与接口/实现没有任何关系,它可以被视为纯粹是带有可选参数的方法的副作用。因此,当您调用该方法时,

public void TestMethod(bool value = false) { /*...*/ }

SomeClass.TestMethod(),它实际上是 SomeClass.TestMethod(false)。如果您在接口上调用此方法,则通过静态类型检查,方法签名具有可选参数。如果您在没有可选参数的派生类实例上调用此方法,则从静态类型检查来看,方法签名没有可选参数,并且必须使用完整参数调用。

由于可选参数的实现方式,这是自然的设计结果。


r
robie2011

感谢您的解释@eric-lippert

这是一些代码示例:

[Fact]
public void TestOptionalMethodArgument()
{
    var implementation = new TestHello();
    IHello @interface = implementation;

    Assert.Equal(23, @interface.Action());
    Assert.Equal(40, implementation.Action());
}

public class TestHello : IHello
{
    public int Action(int number = 40)
        => number;
}

public interface IHello
{
    int Action(int number = 23);
}

J
Jim K
var obj = new MyClass();        
obj.TestMethod(); // compiler error

var obj2 = new MyClass() as MyInterface;
obj2.TestMethod(); // prints false

利用

MyInterface obj = new MyClass();        
obj.TestMethod(); // compiler error

var obj2 = new MyClass() as MyInterface;
obj2.TestMethod(); // prints false

并且都导致错误