Почему я не могу редактировать в отладчике метод, содержащий анонимный метод?

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

Я полагаю, что есть веская техническая причина, почему это так, и, возможно, кто-то знает? Кроме того, кто-нибудь знает, будет ли это исправлено в VS2010?

Спасибо.

Ответов (4)

Решение

Да, есть очень веская причина, почему вы не можете этого сделать. Причина проста - стоимость. Стоимость включения этой функции в C# (или VB) чрезвычайно высока.

Редактирование лямбда-функции - это частный случай класса проблем ENC, которые очень сложно решить с помощью текущей архитектуры ENC (Edit'n'Continue). А именно, очень сложно выполнить ENC любым методом, в котором ENC выполняет одно из следующих действий:

  1. Создает метаданные в виде класса
  2. Редактирует или генерирует универсальный метод

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

Вторая проблема - это строгое ограничение в архитектуре CLR ENC. C# (или VB) ничего не может сделать, чтобы обойти это.

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

Детали довольно запутанные и слишком запутанные для нормального SO-ответа. Я подумал о написании длинного сообщения в блоге на эту тему. Если я дойду до него, я свяжу его с этим конкретным ответом.

Немного жаль, что эта функция частично поддерживается в VB, но не в C#: http://msdn.microsoft.com/en-us/library/bb385795.aspx

Реализация того же поведения в C# снизит уровень боли на 80% для функций, содержащих лямбда-выражения, где нам не нужно изменять лямбда-выражения или любые выражения, которые от них зависят, и, вероятно, не для «чудовищных затрат».

Согласно списку поддерживаемых изменений кода , вы не можете добавлять поля к существующим типам. Анонимные методы компилируются в классы со странными названиями (вроде <>_c__DisplayClass1 ), а именно: типы. Несмотря на то, что ваши модификации анонимного метода могут не включать изменение набора заключенных переменных (их добавление изменит поля существующего класса), я предполагаю, что это причина, по которой невозможно изменить анонимные методы.

В таком случае перезапуск модульного теста должен занять несколько секунд. Честно говоря, мне никогда не нравилась модель «редактировать и продолжать» - вы всегда должны запускать IMO с нуля, на тот случай, если изменение в середине выполнения повлияло бы на код, который выполнялся ранее. Учитывая это, вам лучше использовать модульные тесты, которые можно запускать очень быстро. Если для запуска ваших индивидуальных модульных тестов требуется невыносимое время, вам следует подумать над решением этой проблемы.

РЕДАКТИРОВАТЬ: Что касается того, почему это не работает - вы можете обнаружить, что это работает для некоторых лямбд, но не для других. Лямбда-выражения, которые не захватывают никакие переменные (в том числе this ), кэшируются в частной статической переменной, так что когда-либо создается только один экземпляр делегата. Изменение кода означает повторную инициализацию этой переменной, что, как я подозреваю, может иметь интересные побочные эффекты.