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

brjng4g3  于 2023-05-19  发布在  .NET
关注(0)|答案(7)|浏览(211)

我注意到,在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框架来注入具体类),那么实际上没有必要使用默认值,因为调用者无论如何都必须提供它。

tcomlyy6

tcomlyy61#

更新: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中。

jjhzyzn0

jjhzyzn02#

一个可选的参数只是用一个属性来标记。这个属性告诉编译器在调用点插入该参数的默认值。
当C#代码编译为IL时,调用obj2.TestMethod();obj2.TestMethod(false);替换,而不是在JIT时。
因此,在某种程度上,总是调用方提供带有可选参数的默认值。这对二进制版本控制也有影响:如果更改了默认值但没有重新编译调用代码,它将继续使用旧的默认值。
另一方面,这种断开意味着你不能总是互换地使用具体类和接口。
如果接口方法是implemented explicitly,就不能这样做。

umuewwlo

umuewwlo3#

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

gg0vcinb

gg0vcinb4#

根据我的理解,可选参数有点像宏替换。从方法的Angular 来看,它们实际上不是可选的。其中的一个工件是你看到的行为,如果你转换到一个接口,你会得到不同的结果。

qlvxas9a

qlvxas9a5#

只是想在这里补充一下我的看法,因为其他答案确实提供了合理的解释,但不是完全令我满意的。
可选参数是编译时在调用点注入默认值的语法糖。这与接口/实现没有任何关系,它可以被视为带有可选参数的方法的纯粹副作用。所以,当你调用这个方法时,

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

比如SomeClass.TestMethod(),实际上是SomeClass.TestMethod(false)。如果在接口上调用此方法,则通过静态类型检查,方法签名具有可选参数。如果你在一个派生类的示例上调用这个方法,而这个派生类的示例没有可选参数,那么通过静态类型检查,这个方法的签名没有可选参数,并且必须用完整的参数来调用。
由于可选参数的实现方式,这是自然的设计结果。

oo7oh9g9

oo7oh9g96#

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

并且两者都导致false

6yoyoihd

6yoyoihd7#

谢谢你的解释@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);
}

相关问题