Подтвердить что ты не робот

Параметр Lambda, конфликтующий с полем класса при доступе к полю в более поздней области

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

Вот пример кода:

public delegate void TestDelegate(int test);

public class Test
{
    private int test;

    private void method(int aaa)
    {
        TestDelegate del = test => aaa++;

        test++;
    }

    public static void Main()
    {
    }
}

Вот ошибки компиляции (вывод ideone):

prog.cs(11,3): error CS0135: `test' conflicts with a declaration in a child block
prog.cs(9,22): (Location of the symbol related to previous error)
Compilation failed: 1 error(s), 0 warnings

Строка 11 содержит test++, строка 9 содержит лямбда.

Кстати, Visual Studio 2013 дает другую ошибку:

'test' conflicts with the declaration 'Namespace.Test.test'

Ошибка происходит только при инкрементах только в строке 11.

Код компилируется успешно, если я прокомментирую либо строку 9 (лямбда), либо строку 11 (приращение).

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

Я не могу обдумать это... Что именно я сделал неправильно? И что означают сообщения об ошибках в этом случае?

ИЗМЕНИТЬ после всех отличных ответов:

Итак, я думаю, что, наконец, понял правило, которое я сломал. Это не хорошо сказано в спецификации С# (7.6.2.1, см. Ответ Джона Скита для цитаты). То, что должно было означать, это что-то вроде:

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

Не стандартная стандартная фраза, но я надеюсь, вы поняли, что я имею в виду. Это правило должно было разрешить это:

{
    int a;
}

{
    int a;
}

потому что ни одна из областей двух переменных a не может быть "видима" из другой области;

и запретить это:

{
    int a;
}

int a;

потому что второе объявление переменной "видно" из первой области видимости переменной

и запретить это:

class Test
{
    int test;

    void method()
    {
        {
            int test;
        }

        test++;
    }
}

потому что приращение поля можно "увидеть" из области блока (это не объявление не имеет значения).

Кажется, что С# 6 изменило это правило, в частности, чтобы закончить последний пример (и мой исходный код), хотя я действительно не понимаю, как именно.

Пожалуйста, исправьте меня, если я допустил некоторые ошибки в этих примерах.

4b9b3361

Ответ 1

К сожалению, отрицательный ответ Шрирама Шактивеля верен. У С# есть правило, которое я написал несколько раз, что требует, чтобы каждое использование одного и того же простого имени в блоке имело то же значение.

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

Вы можете прочитать мои статьи об этом правиле и работу, которую я сделал для улучшения сообщения об ошибке, здесь:

http://ericlippert.com/tag/simple-names/

(Начните снизу, они находятся в обратном хронологическом порядке.)

Джон и другие правильно отмечают, что в предварительных версиях С# 6 вы не получите ошибку для своего кода. Я считаю, что после того, как я покинул команду, было сделано больше работы по этому условию ошибки, и, вероятно, было смягчено, чтобы быть более снисходительным.

Принцип "одно имя должно означать только одно" - это хорошо, но сложно реализовать, сложно объяснить и источник множества вопросов на этом сайте, поэтому, возможно, проектная группа решила идти с более простым для объяснения и реализации правила. Что именно это правило, я не знаю; Я еще не успел просмотреть исходный код Roslyn и посмотреть, как эта часть кода эволюционировала с течением времени.

ОБНОВЛЕНИЕ: Мои шпионы в команде Roslyn сообщают мне, что это было совершено 23891e, что значительно сократит поиск.: -)

ОБНОВЛЕНИЕ: правило ушло навсегда; см. комментарий Джона для деталей.

Ответ 2

Эрик Липперт опубликовал блог об этом Простые имена не так просты.

Простые имена (без полного имени) всегда могут означать только одну вещь в блоке кода. Если вы нарушите его, будет ошибка компилятора CS0135.

В вашем методе method, test - простое имя, что означает две вещи. Это запрещено в С#.

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

private void method(int aaa)
{
    TestDelegate del = test => aaa++;

    this.test++;
}

Или, если вы сделаете доступ к тестовому полю в другом блоке, компилятор будет с удовольствием компилировать.

private void method(int aaa)
{
    TestDelegate del = (int test) => aaa++;

    {
        test++;
    }
}

Теперь у вас нет двух разных значений для одного и того же простого имени test. потому что второй тест живет в другом блоке.

На данный момент (апрель 2015 г.) этот ответ действителен. Начиная с С# 6.0 все изменилось. Это правило ушло. Подробнее см. Jon.

Ответ 3

Я поднял Roslyn issue 2110 для этого - спецификация С# 6 меняется, чтобы это разрешить. Mads Torgersen указал, что изменение по дизайну:

Правило было хорошо спланировано, поскольку предполагалось свести к минимуму риск "перемещать код вокруг" таким образом, чтобы он молчал. Однако все, о чем мы говорили, только, казалось, знали о правиле, когда оно вызывало у них ненужное горе, - никто никогда не был спасен своими запретами.

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

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

class Test
{
    static int test;

    static void Main()
    {
        {
            int test = 10;
        }
        test++;
    }
}

Связанный раздел спецификации С# 5 равен 7.6.2.1, который дает это правило:

7.6.2.1 Инвариантное значение в блоках

Для каждого вхождения данного идентификатора в виде полного простого имени (без списка аргументов типа) в выражении или деклараторе, в локальном пространстве объявления переменных (§3.3), немедленно включающем это вхождение, каждое другое вхождение того же идентификатор как полное простое имя в выражении или деклараторе должен ссылаться на один и тот же объект. Это правило гарантирует, что значение имени всегда будет одинаковым в данном блоке, блоке переключателя, for-, foreach- или use-statement или анонимной функции.

Лично я считаю, что это не более чем идеальный бит спецификации. Неясно, включает ли "внутри данного блока" вложенные блоки. Например, это нормально:

static void Main()
{
    {
        int x = 10;
    }

    {
        int x = 20;
    }
}

... несмотря на то, что x используется для обозначения разных переменных в блоке "верхнего уровня" метода Main, если вы включаете вложенность. Поэтому, если вы считаете этот блок, он нарушает утверждение о том, что "это правило гарантирует, что значение имени всегда одно и то же в пределах данного блока [...]" Однако я считаю, что блок не проверен для этого, потому что он не является "объявляющим" пространством декларации переменных любого использования x.

Итак, на мой взгляд, ошибка согласуется с первой цитированной частью спецификации, но не согласуется с окончательным предложением, которое является примечанием в спецификации ECMA.

Я запомню плохую формулировку спецификации и попытаюсь исправить ее для следующей версии спецификации ECMA.