Почему анонимные методы внутри структур не могут получить доступ к членам экземпляра 'this'
У меня есть код, похожий на следующий:
struct A
{
void SomeMethod()
{
var items = Enumerable.Range(0, 10).Where(i => i == _field);
}
int _field;
}
... и затем я получаю следующую ошибку компилятора:
Anonymous methods inside structs can not access instance members of 'this'.
Кто-нибудь может объяснить, что происходит здесь.
Ответы
Ответ 1
Переменные записываются по ссылке (даже если они были фактически типами значений, тогда бокс выполняется).
Однако this
в ValueType (struct) не может быть помещен в бокс, и, следовательно, вы не можете его захватить.
У Эрика Липперта есть хорошая статья о неожиданностях захвата ValueTypes. Позвольте мне найти ссылку
Обратите внимание на комментарий Криса Синклера:
В качестве быстрого исправления вы можете сохранить структуру в локальной переменной: A thisA = this; var items = Enumerable.Range(0, 10).Where(i => i == thisA._field);
- Chris Sinclair 4 минуты назад
Остерегайтесь того, что это создает удивительные ситуации: идентификатор thisA
не совпадает с this
. Более явно, если вы решите сохранить лямбду дольше, она будет иметь помеченную копию thisA
, записанную по ссылке, и не фактический экземпляр, который SomeMethod
был.
Ответ 2
Когда у вас есть анонимный метод, он будет скомпилирован в новый класс, этот класс будет иметь один метод (тот, который вы определяете). Он также будет ссылаться на каждую переменную, которую вы использовали, вне рамки анонимного метода. Важно подчеркнуть, что это ссылка, а не копия этой переменной. "lambdas закрывает переменные, а не значения", как говорится. Это означает, что если вы закрываете переменную за пределами области лямбда, а затем изменяете эту переменную после определения анонимного метода (но перед ее вызовом), вы увидите измененное значение, когда вы его вызываете).
Итак, в чем суть всего этого. Ну, если вы хотите закрыть this
для структуры, которая является типом значений, возможно, что лямбда переживет структуру. Анонимный метод будет находиться в классе, а не в структуре, поэтому он будет находиться в куче, дожидаться до тех пор, пока это необходимо, и вы можете свободно передавать ссылку на этот класс (прямо или косвенно) там, где вы хотите.
Теперь представьте, что у нас есть локальная переменная со структурой типа, который вы здесь определили. Мы используем этот именованный метод для генерации лямбда и предположим на мгновение, что возвращается запрос items
(вместо метода void
). Затем мог бы сохранить этот запрос в другой экземпляре (вместо локальной) переменной и повторить этот запрос через некоторое время по другому методу. Что будет здесь? По сути, мы бы взяли ссылку на тип значения, который был в стеке, когда он больше не находится в сфере видимости.
Что это значит? Ответ: мы понятия не имеем. (Пожалуйста, просмотрите ссылку, это как раз суть моего аргумента.) Данные могут быть одинаковыми, их можно было бы обнулить, они могли быть заполнены совершенно разными объектами, нет способа узнать. С# идет на большие расстояния, как язык, чтобы вы не делали такие вещи. Такие языки, как C или С++, не пытаются так сильно помешать вам стрелять в собственную ногу.
Теперь, в этом конкретном случае, возможно, что вы не собираетесь использовать лямбда вне области действия this
, но компилятор этого не знает, и если он позволяет вам создавать lambda, у него нет способа определить, выставляете ли вы его или нет, таким образом, чтобы он мог пережить this
, поэтому единственный способ предотвратить эту проблему состоит в том, чтобы запретить некоторые случаи, которые на самом деле не проблематичны.