Почему добавление типа возврата в метод возврата void вызывает исключение MissingMethodException - PullRequest
10 голосов
/ 07 февраля 2012

У меня есть приложение .NET, которое использует сборку (.dll), которая определяет какой-то метод:

    public void DoSomething()
    {
        // Do work
    }

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

    public string DoSomething()
    {
        // Do work
        return "something";
    }

Почему код, который использует этот метод, не работает на System.MissingMethodException ?

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

Почему это изменение нарушает код?

Ответы [ 5 ]

16 голосов
/ 07 февраля 2012

Другие ответы, в которых говорится, что вы изменили сигнатуру метода и, следовательно, должны перекомпилировать вызывающие, верны. Я подумал, что мог бы добавить дополнительную информацию по этому вопросу:

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

Это совершенно верно. Теперь рассмотрим вопрос: как писать код, который не использует данные ? Похоже, вы работаете под совершенно ложным предположением, что не использует значение , не требует кода, но не использует значение, безусловно, требует кода!

Предположим, у вас есть методы:

static int M1(int y) { return y + 1; }
static void M2(int z) { ... }

и у вас есть звонок

int x;
x = M1(123);

Что происходит на уровне IL ? следующее:

  • Выделите место во временном пуле для x.
  • Нажмите 123 в стеке
  • Вызов M1.
  • Нажмите 1 в стеке. Стек теперь 1, 123
  • Добавьте две верхние вещи в стек. Это выскакивает и подталкивает результат. Стек теперь 124.
  • Возврат к звонящему
  • стек по-прежнему 124.
  • Сохранить значение в стеке во временном хранилище для x. Это вытолкнет стек, поэтому стек теперь пуст.

Предположим, теперь вы делаете:

M1(345);

Что происходит? То же самое :

  • Нажмите 345 в стеке
  • Вызов M1.
  • Нажмите 1 в стеке. Стек теперь 1, 345
  • Добавьте две верхние вещи в стек. Это выскакивает и подталкивает результат. Стек теперь 346.
  • Вернуться к звонящему
  • Стек все еще 346.

Но нет инструкции, которая хранит значение в стеке, поэтому мы должны выполнить команду pop:

  • Выкинуть неиспользуемое значение из стека.

Теперь предположим, что вы звоните

M2(456);

Что происходит?

  • Нажмите 456 в стеке
  • Вызов M2.
  • М2 делает свое дело. Когда он возвращается к вызывающей стороне, стек пуст, потому что он возвращается пусто.
  • Стек теперь пуст, поэтому ничего с него не вытряхивайте.

Теперь вы понимаете, почему изменение метода с возврата void на возврат значения является критическим изменением? Каждый вызывающий теперь должен выкинуть неиспользуемое значение из стека . Для выполнения ничего с данными все еще требуется очистить их от стека . Вы неправильно выравниваете стек, если не выталкиваете это значение; CLR требует, чтобы стек был пуст в начале каждого оператора, чтобы избежать такого смещения.

2 голосов
/ 07 февраля 2012

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

Не имеет значения, используется ли значение или нет - среда выполнения не может найти void YourClass::DoSomething() и даже не пытается найти string YourClass::DoSomething().

Если бы такое изменение было возможно, вы могли бы легко взорвать среду выполнения, вызвав переполнение / переполнение стека.

1 голос
/ 07 февраля 2012

Потому что вы сделали критическое изменение API.Вы изменили сигнатуру метода в базовом классе или в интерфейсе.

Звонящие связаны с этим методом.В IL ссылка на метод - это не только ссылка на тип и его метод с некоторым указателем на метод, но и ссылки на метод вызывающей стороны включают полную сигнатуру метода.

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

Это случилось со мной тоже.Вы правы, что никто не мог использовать тип возвращаемого значения, поэтому это изменение безопасно, но вам нужно перекомпилировать все затронутые цели.

1 голос
/ 07 февраля 2012

Поскольку вы изменили подпись метода.

Когда внешний код должен найти метод, он должен убедиться, что он вызывает правильно. Часть этой информации хранится в виде подписи во время компиляции - информация подписи включает тип возвращаемого значения (независимо от того, используется ли он где-либо на самом деле).

Что касается CLR, метод с возвращаемым типом void больше не существует - следовательно, MissingMethodException.

0 голосов
/ 07 февраля 2012

Создатели C # решили, что следует строго соблюдать сигнатуры методов.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...