Почему эти два метода не являются двусмысленными?

Это подпись для метода Ok() в ApiController:

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.

Как компилятор знает, какой метод запускать?

Ответы

Ответ 1

Джон Скит ответил на аналогичный вопрос (без усложнения наследования) здесь:

Когда у компилятора есть два варианта, равных в противном случае, он будет использовать перегрузку, которая не нуждается в каких-либо необязательных необязательных параметрах, чем тот, который делает...

В вашем случае, однако, выбирается метод из RestController, потому что это более производный класс. Джон неплохо занимается подробным рассмотрением этой темы в своей книге С# в глубине - посмотрите раздел наследования этой страницы, который по существу заявляет, что компилятор предпочтет метод для фактического класса экземпляра перед методами на менее производных классах.

Ответ 2

EDIT:

Я оставляю свой первоначальный ответ для потомков, потому что я думаю, что он позволяет вам визуализировать вещи, но НЕ БУДЕТ КОНФЛИКТОВ! Компилятор фактически не обрабатывает необязательный параметр как синтаксический сахар для переопределенного метода. Он рассматривает его как единый метод с необязательным параметром. Пыльный ответ, отметив, что "метод из RestController выбирается, потому что он является более производным классом". Правильно.

ОРИГИНАЛ (с видимыми изменениями для правильности):

Потому что они НЕ двусмысленны. Чтобы быть неоднозначным, методы должны иметь одну и ту же подпись. Тот факт, что параметр string message имеет значение по умолчанию null эффективно создает BEHAVES, как если бы он создавал два вызываемых переопределения, один из которых HIDES оригинальный метод, и один из которых явно вызываемый со строкой.

Эффективно выполняете , создавая такое же поведение, как если бы вы это делали:

public class RestController : ApiController
{
    protected new OkResult Ok()
    {
        return Ok(null);
    }

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

Вы обнаружите, что нет прямого вызова ApiController.Ok() из PersistenceRestController.

Если вы хотите вызвать ApiController.Ok() из RestController, вам нужно будет использовать базовый keywoard: 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();
    }
}

Когда вы вызываете Bar() в свой класс Foo3, среда выполнения будет искать после метода внутри класса Foo3. Если он найден, выполните его, в противном случае перейдите в верхний класс: Foo2 и обратите внимание на метод Bar. Есть ли? да! поэтому выполните его! поэтому, когда вы вызываете Ok, ваша версия RestController s будет выполнена.

Но также Foo2.Bar(string message = null) не будет конфликтовать с Foo.Bar(), потому что они НЕ двусмысленны, как сказал @DimitarTsonev. Таким образом, ваш код будет работать нормально.

И, как насчет вызова Foo.Bar() из Foo3? Вы должны использовать кастинг здесь:

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

Это два разных метода, потому что второй имеет необязательный аргумент.

Однако обратите внимание, что второй метод, вызываемый без аргументов, фактически выполнит первый, что может привести к неожиданному поведению.