Чому компілятор 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
Тисяча

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вираження". Отже, моя мета тут - придумати ідею, чому вони вирішили не перевіряти статичність проти екземпляра до часу виконання. Я б сказав, ця перевірка відбувається в обов'язковий час .
Джеппе Стіг Нільсен

досить справедливо, але це все ще не пояснює, чому в цьому випадку компілятор не може вирішити виклик методу екземпляра. Іншими словами, спосіб компілятора робить роздільну здатність спрощений - він не розпізнає простий випадок, як мій приклад, коли немає можливості, що він не може вирішити виклик. Іронія полягає в тому, що, маючи єдиний метод Bar () з динамічним параметром, компілятор ігнорує той метод Bar ().
Майк Скотт

45
Я написав цю частину компілятора C #, і Jeppe має рацію. Будь ласка, проголосуйте за це. Розв’язання перевантаження відбувається перед тим, як перевірити, чи є даний метод статичним чи екземплярним методом, і в цьому випадку ми відкладаємо роздільну здатність перевантаження на час виконання, а також перевіряємо статичність / екземпляр до часу виконання. Крім того, компілятор докладає "найкращих зусиль" для статичного пошуку динамічних помилок, які абсолютно не є вичерпними.
Кріс Берроуз

30

Foo має параметр "x", який є динамічним, а це означає, що Bar (x) є динамічним виразом.

Було б цілком можливо, щоб в Прикладі були такі методи, як:

static Bar(SomeType obj)

У такому випадку правильний метод буде вирішений, тому твердження Bar (x) цілком справедливо. Те, що існує метод екземпляра Bar (x), не має значення і навіть не розглядається: за визначенням , оскільки Bar (x) є динамічним виразом, ми відклали роздільну здатність до виконання.


14
але коли ви виймаєте метод Bar bar, він більше не збирається.
Джастін Харві

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

1
@Marc, оскільки немає іншого методу Bar (), ви не відповідаєте на питання. Чи можете ви пояснити це, враховуючи, що існує лише один метод Bar () без перевантажень? Навіщо відкладати на виконання, коли жоден інший метод не може бути названий? Або є? Примітка. Я відредагував код, щоб закріпити клас, який все ще компілюється.
Майк Скотт

1
@mike щодо того, чому відкладати час виконання: тому що саме це означає
Marc Gravell

2
@Mike неможливий не сенс; що важливо - чи потрібно . Вся суть динаміки полягає в тому, що це не робота компілятора.
Марк Гравелл

9

"Динамічний" вираз буде зв'язаний під час виконання, тому якщо ви визначаєте статичний метод з правильним підписом або метод екземпляра, компілятор не перевірятиме його.

"Правильний" метод буде визначений під час виконання. Компілятор не може знати, чи є там дійсний метод під час виконання.

Ключове слово "динамічний" визначається для динамічних мов та мов скриптів, де Метод можна визначити в будь-який час, навіть під час виконання. Божевільний матеріал

Тут зразок, який обробляє вставки, але не містить рядків, оскільки метод є на екземплярі.

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.