为啥这两种方法没有歧义?

Posted

技术标签:

【中文标题】为啥这两种方法没有歧义?【英文标题】:Why are these two methods not ambiguous?为什么这两种方法没有歧义? 【发布时间】:2015-10-06 02:46:51 【问题描述】:

这是ApiControllerOk() 方法的签名:

protected internal virtual OkResult Ok();

这是我的RestController 类(从ApiController 扩展)中的方法:

// Note that I'm not overriding base method
protected IHttpActionResult Ok(string message = null);

由于OkResult实现了IHttpActionResult,所以这两个方法都可以这样调用:

IHttpActionResult result = Ok();

事实上,这就是我在我的应用程序中所做的。

我的班级 PersistenceRestController(从 RestController 扩展而来)有这些代码行:

protected override async Task<IHttpActionResult> Delete(Key id)

    bool deleted = //... Attempts to delete entity
    if(deleted) return Ok();
    else return NotFound();

这编译得很好,并且没有关于方法歧义的警告。这是为什么呢?

PersistenceRestController 还从ApiController 继承了受保护的方法,因此它应该具有Ok() 的两个版本(确实如此)。

执行时,执行的方法是我RestController中的方法。

编译器如何知道运行哪个方法?

【问题讨论】:

@M.kazemAkhgary 两种方法都返回相同的接口 第一个是internal。您是从不同的程序集中致电 Ok() 吗? OkResult result = Ok();会发生什么? 可选参数从调用者那里得到注入的默认值,所以它们是两个不同参数的方法 【参考方案1】:

Jon Skeet 回答了一个类似的问题(没有继承的复杂性)here:

当编译器有两个其他相等的选项可供选择时,它将使用不需要使用任何未提供的可选参数的重载,而不是使用的重载...

但是,在您的情况下,选择了 RestController 中的方法,因为它是更多派生类。 Jon 在他的书C# in Depth 中很好地解决了这个主题——看看那个页面的继承部分,它基本上表明编译器会更喜欢实际实例类的方法,而不是较少派生类的方法。

【讨论】:

@JamieHumphries - 对 base.Ok() 的调用将从 RestController 调用 ApiController.Ok(),但不会从 PersistenceRestController 调用。我相信 (this as ApiController).Ok() 将不可用,因为您正在转换为 ApiController,Ok() 方法在其上受到保护,而不是内部或公共。【参考方案2】:

编辑:

我将我的原始答案留给后人,因为我认为它可以让您将事物可视化,但不要混淆!编译器实际上并不将可选参数视为重写方法的语法糖。它将其视为具有可选参数的单个方法。 Dusty 的回答中提到“选择来自 RestController 的方法是因为它是更多派生类”,这是正确的。

ORIGINAL(带有可见的修改以确保正确性):

因为它们不是模棱两可的。为了模棱两可,方法需要具有相同的签名。 string message 参数的默认值为 null 的事实实际上 create 表现得好像它创建了两个可调用的覆盖,其中一个隐藏了原始方法,其中一个明显可以用字符串调用.

您正在有效地做创建与您要这样做相同的行为:

public class RestController : ApiController

    protected new OkResult Ok()
    
        return Ok(null);
    

    protected OkResult Ok(string message)
    
        // Do your thing...
    

你会发现没有办法直接从 PersistenceRestController 调用 ApiController.Ok()。

如果你想从 RestController 调用 ApiController.Ok(),你必须使用基本键盘:base.Ok();

【讨论】:

【参考方案3】:

虽然@DimitarTsonev 和@Dusty 讲述的是真实的事情,但你的答案介于他们的答案之间。在这里,你有继承的情况。查看这些课程:

public class Foo 
    public void Bar() 
    


public class Foo2 : Foo
    public void Bar(string message = null) 
    


public class Foo3 : Foo2
    public void Test()
        Bar();
    

当您在 Foo3 类中调用 Bar() 时,运行时将在 Foo3 类中的方法之后查找。如果找到,就执行它,否则转到***类:Foo2 并关注Bar 方法。有没有?是的!所以执行它!这就是为什么当您调用Ok 时,您的RestControllers 版本会被执行。

而且,Foo2.Bar(string message = null) 不会与Foo.Bar() 冲突,因为它们并不像@DimitarTsonev 所说的那样模棱两可。因此,您的代码可以正常工作。

还有,从Foo3 调用Foo.Bar() 怎么样?你必须在这里使用强制转换:

public class Foo3 : Foo2 
    public void Test() 
        Bar(); // this will execute Foo2.Bar()
    
    public void Test2() 
        ((Foo)this).Bar(); // this one will execute Foo.Bar()
    

【讨论】:

【参考方案4】:
public class Foo

    public void Bar()
    
    

    public void Bar(string message = null)
    
    

这是两种不同的方法,因为第二种方法有可选参数。

但是,请注意,不带参数调用的第二个方法实际上会执行第一个方法,这可能会产生一些意想不到的行为。

【讨论】:

这与所问的情况不同,因为没有可选参数的方法应该位于基类上(调用Bar() 时会改变行为)。

以上是关于为啥这两种方法没有歧义?的主要内容,如果未能解决你的问题,请参考以下文章

为啥这两种轮换方法会给我不同的用户体验?

在 React Native 中,这两种方法中哪种 Async Await 方法更好,为啥?

为啥这两种将图像从 SQL CE 加载到 WPF 图像中的方法会产生不同的结果?

为啥这两种变体之间的速度差异如此之大?

这两种方法有啥区别?

为啥 p[:] 在这两种情况下的工作方式不同?