Невозможно использовать фиксированное локальное выражение внутри лямбда-выражения

У меня есть проект XNA 3.0, который отлично скомпилирован в VS2008, но дает ошибки компиляции в VS2010 (с XNA 4.0 CTP). Ошибка:

Нельзя использовать фиксированный локальный 'depthPtr' внутри анонимного метода, лямбда-выражения или выражения запроса.

depthPtr — это fixed float* в массив, который используется внутри лямбда-выражения Parallel.For из System.Threading. Как я уже сказал, это скомпилировано и отлично работает на VS2008, но не на VS2010, даже при нацеливании на .NET 3.5.

Изменилось ли это в .NET 4.0, и даже в этом случае не должно ли оно по-прежнему компилироваться, когда я выбираю .NET 3.5 в качестве целевой платформы? Поиск по термину «Невозможно использовать фиксированный локальный» дает ровно один (бесполезный) результат как в Google, так и в Bing.

Если это изменилось, то в чем причина этого? Я могу себе представить, что захват типа указателя fixed в замыкании может стать немного странным, вот почему? Я так понимаю, это плохая практика? И прежде чем кто-нибудь спросит: нет, использование указателей здесь не совсем критично. Я все равно хотел бы знать :)

EDIT: В соответствии с запросом образец кода (очевидно, не из моей программы), который воспроизводит ошибку:

static unsafe void Main(string[] args)
{
  float[] array = new float[10];

  fixed (float* ptr = array)
  {
    Parallel.For(0, 10, i =>
    {
      ptr[i] = i;
    });
  }
}

Вышеприведенное компилируется в VS2008 (ну, кроме ссылки на Parallel, но подойдет и любое другое лямбда-выражение), но не в VS2010.


person JulianR    schedule 13.05.2010    source источник
comment
не могли бы вы опубликовать код, который вызывает ошибку.   -  person luke    schedule 13.05.2010
comment
Ну, тут особо нечего публиковать, это именно то, о чем говорит ошибка: использование указателя внутри лямбда-выражения.   -  person JulianR    schedule 13.05.2010
comment
Тем не менее, опубликовать короткий, но полный фрагмент кода, с которым мы можем поэкспериментировать, было бы хорошо (tm).   -  person Jon Skeet    schedule 13.05.2010


Ответы (5)


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

person Logan Capaldo    schedule 13.05.2010
comment
Ах, так он просто устанавливает версию среды выполнения и не использует более старый компилятор? Имеет смысл. Что касается закрепления, да, это риск, но я мог бы так же легко объявить поле экземпляра для типа, который также живет дольше, чем фиксированный блок, заставляя его указывать на недопустимое местоположение. И все же это возможно. - person JulianR; 13.05.2010
comment
Я не думаю, что было бы возможно статически проверить все сценарии, в которых закрепленный указатель может стать недействительным, в конечном итоге кто-то всегда может использовать GCHandle. Эта проверка, вероятно, представляет собой попытку избежать неочевидных проблем, которые могут возникнуть из-за кода, подобного этому. - person Logan Capaldo; 13.05.2010

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

    static unsafe void UnsafeTest(string[] args) {
        float[] array = new float[10];

        fixed(float* ptr = array) {
            UnsafeOps ops = new UnsafeOps();
            ops.p = ptr;

            Parallel.For(0, 10, ops.Lambda);
        }
    }

    unsafe class UnsafeOps {
        public float* p;
        public unsafe void Lambda(int value) {
            p[value] = value;
        }
    }

Мне кажется, что в .NET 4 добавлена ​​какая-то половинчатая попытка запретить фиксированный доступ к памяти в компиляторе. В приведенном выше блоке кода вы можете определить UnsafeOps вне блока fixed и получить доступ к массиву после блока fixed. Так что не идеально...

person Igor Zevaka    schedule 13.05.2010

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

person LukeN    schedule 13.05.2010

Компилятор правильно отвергает этот код. Fixed можно использовать только для локальных переменных, а переменные, захваченные замыканием, не являются локальными переменными, они переносятся в класс, используемый для поддержания состояния замыкания.

person Ben Voigt    schedule 13.05.2010

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

person Femaref    schedule 13.05.2010