Почему компилятор C # не кодирует ошибку, когда статический метод вызывает метод экземпляра?


110

В следующем коде есть статический метод Foo(), вызывающий метод экземпляра Bar():

public sealed class Example
{
    int count;

    public static void Foo( dynamic x )
    {
        Bar(x);
    }

    void Bar( dynamic x )
    {
        count++;
    }
}

Он компилируется без ошибок *, но генерирует исключение связывания во время выполнения. Удаление динамического параметра для этих методов вызывает ошибку компилятора, как и ожидалось.

Итак, почему наличие динамического параметра позволяет компилировать код? ReSharper также не показывает это как ошибку.

Изменить 1: * в Visual Studio 2008

Изменить 2: добавлено, sealedтак как возможно, что подкласс может содержать статический Bar(...)метод. Даже запечатанная версия компилируется, когда во время выполнения невозможно вызвать какой-либо другой метод, кроме метода экземпляра.


8
+1 за очень хороший вопрос
cuongle

40
Это вопрос Эрика-Липперта.
Оливье Жако-Декомб

3
Я почти уверен, что Джон Скит тоже знает, что с этим делать;) @ OlivierJacot-Descombes
Thousand

2
@Olivier, Джон Скит, вероятно, хотел, чтобы код компилировался, поэтому компилятор позволяет это :-))
Майк Скотт

5
Это еще один пример того, почему вы не должны использовать его, dynamicесли в этом нет необходимости.
Обслуживание

Ответы:


71

ОБНОВЛЕНИЕ: ответ ниже был написан в 2012 году, до введения C # 7.3 (май 2018 года) . В разделе « Что нового в C # 7.3» , в разделе « Улучшенные кандидаты на перегрузку» , пункт 1, объясняется, как изменились правила разрешения перегрузки, чтобы нестатические перегрузки отбрасывались раньше. Итак, ответ ниже (и весь этот вопрос) на данный момент в основном представляет только исторический интерес!


(До C # 7.3 :)

По какой-то причине разрешение перегрузки всегда находит лучшее совпадение перед проверкой статического и нестатического. Пожалуйста, попробуйте этот код со всеми статическими типами:

class SillyStuff
{
  static void SameName(object o) { }
  void SameName(string s) { }

  public static void Test()
  {
    SameName("Hi mom");
  }
}

Это не будет компилироваться, потому что лучшая перегрузка - это перегрузка string. Но послушайте, это метод экземпляра, поэтому компилятор жалуется (вместо того, чтобы использовать вторую по величине перегрузку).

Дополнение: Итак, я думаю, что объяснение dynamicпримера исходного вопроса заключается в том, что для обеспечения единообразия, когда типы являются динамическими, мы также сначала находим лучшую перегрузку (проверяя только номер параметра и типы параметров и т.д., а не статические и не статические). -статический), и только потом проверять статичность. Но это означает, что статическая проверка должна дождаться времени выполнения. Отсюда наблюдаемое поведение.

Позднее добавление: некоторую предысторию того, почему они выбрали этот забавный порядок, можно вывести из этого сообщения в блоге Эрика Липперта .


В исходном вопросе нет никаких перегрузок. Ответы, показывающие статическую перегрузку, не актуальны. Неверно отвечать «хорошо, если ты написал это ...», поскольку я этого не писал :-)
Майк Скотт

5
@MikeScott Я просто пытаюсь убедить вас, что разрешение перегрузки в C # всегда выглядит следующим образом: (1) Найдите наилучшее совпадение без учета статических / нестатических. (2) Теперь мы знаем , что перегрузка в использовании, а затем проверить статики. Из-за этого, когда он dynamicбыл представлен в языке, я думаю, что разработчики C # сказали: «Мы не будем рассматривать (2) время компиляции, когда это dynamicвыражение». Итак, моя цель здесь - придумать, почему они решили не проверять статичность по сравнению с экземпляром до времени выполнения. Я бы сказал, эта проверка происходит во время привязки .
Jeppe Stig Nielsen

справедливо, но это все еще не объясняет, почему в этом случае компилятор не может разрешить вызов метода экземпляра. Другими словами, способ, которым компилятор выполняет разрешение, является упрощенным - он не распознает простой случай, такой как мой пример, когда нет возможности, что он не может разрешить вызов. Ирония заключается в том, что, имея единственный метод Bar () с динамическим параметром, компилятор игнорирует этот единственный метод Bar ().
Майк Скотт,

45
Я написал эту часть компилятора C #, и Джепп прав. Пожалуйста, проголосуйте за это. Разрешение перегрузки происходит до проверки того, является ли данный метод статическим или методом экземпляра, и в этом случае мы откладываем разрешение перегрузки на время выполнения и, следовательно, также на статическую проверку / проверку экземпляра до времени выполнения. Кроме того, компилятор делает все возможное, чтобы статически находить динамические ошибки, что не является исчерпывающим.
Крис Берроуз,

30

У Foo есть параметр «x», который является динамическим, что означает, что Bar (x) является динамическим выражением.

В примере было бы вполне возможно иметь такие методы, как:

static Bar(SomeType obj)

В этом случае правильный метод будет разрешен, поэтому утверждение Bar (x) совершенно верно. Тот факт, что существует метод экземпляра Bar (x), не имеет значения и даже не рассматривается: по определению , поскольку Bar (x) является динамическим выражением, мы отложили разрешение до времени выполнения.


14
но когда вы извлекаете метод экземпляра Bar, он больше не компилируется.
Джастин Харви,

1
@Justin интересно - предупреждение? Или ошибка? В любом случае, это может быть проверка только до группы методов, оставляя полное разрешение перегрузки для времени выполнения.
Марк Гравелл

1
@Marc, поскольку другого метода Bar () нет, вы не отвечаете на вопрос. Можете ли вы объяснить это, учитывая, что существует только один метод Bar () без перегрузок? Зачем откладывать время выполнения, если нет возможности вызвать какой-либо другой метод? Или есть? Примечание. Я отредактировал код, чтобы запечатать класс, который все еще компилируется.
Майк Скотт

1
@mike насчет того, почему откладывать время выполнения: потому что именно это означает
Марк Грейвелл

2
@ Майк невозможно, не в этом суть; важно то, требуется ли это . Вся суть динамики в том, что это не работа компилятора.
Марк Гравелл

9

«Динамическое» выражение будет привязано во время выполнения, поэтому, если вы определяете статический метод с правильной сигнатурой или методом экземпляра, компилятор не будет его проверять.

«Правильный» метод будет определен во время выполнения. Компилятор не может знать, есть ли там допустимый метод во время выполнения.

Ключевое слово «dynamic» определено для динамических языков и языков сценариев, где метод может быть определен в любое время, даже во время выполнения. Сумасшедшие вещи

Здесь образец, который обрабатывает целые числа, но не строки, потому что метод находится в экземпляре.

class Program {
    static void Main(string[] args) {
        Example.Foo(1234);
        Example.Foo("1234");
    }
}
public class Example {
    int count;

    public static void Foo(dynamic x) {
        Bar(x);
    }

    public static void Bar(int a) {
        Console.WriteLine(a);
    }

    void Bar(dynamic x) {
        count++;
    }
}

Вы можете добавить метод для обработки всех "неправильных" вызовов, которые невозможно обработать.

public class Example {
    int count;

    public static void Foo(dynamic x) {
        Bar(x);
    }

    public static void Bar<T>(T a) {
        Console.WriteLine("Error handling:" + a);
    }

    public static void Bar(int a) {
        Console.WriteLine(a);
    }

    void Bar(dynamic x) {
        count++;
    }
}

Разве вызывающий код в вашем примере не должен быть Example.Bar (...) вместо Example.Foo (...)? Разве Foo () не имеет отношения к вашему примеру? Я не очень понимаю твой пример. Почему добавление статического универсального метода может вызвать проблему? Не могли бы вы отредактировать свой ответ, включив этот метод вместо того, чтобы указывать его как вариант?
Майк Скотт

но в опубликованном мной примере есть только один метод экземпляра и нет перегрузок, поэтому во время компиляции вы знаете, что нет возможных статических методов, которые можно было бы разрешить. Только если вы добавите хотя бы один, ситуация изменится и код станет действительным.
Майк Скотт

Но в этом примере по-прежнему используется более одного метода Bar (). В моем примере есть только один метод. Таким образом, нет возможности вызвать какой-либо статический метод Bar (). Вызов может быть разрешен во время компиляции.
Майк Скотт,

@Mike может быть! = Есть; с динамикой этого делать не требуется
Марк Гравелл

@MarcGravell, поясните, пожалуйста?
Майк Скотт
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.