LINQ, выходные аргументы и ошибка «Использование неназначенной локальной переменной»

У меня есть код, похожий на следующий.

class MyClass<TEnum> where TEnum : struct
{
    public IEnumerable<TEnum> Roles { get; protected set; }

    public MyClass()
    {
        IEnumerable<string> roles = ... ;

        TEnum value;
        Roles = from r in roles
                where Enum.TryParse(r, out value)
                select value;   // <---- ERROR HERE!
    }
}

Однако в строке, указанной выше, я получаю сообщение об ошибке:

Использование неназначенной локальной переменной 'value'

Мне кажется, что value всегда будет инициализирован в этом случае, так как это параметр out для Enum.TryParse.

Это ошибка компилятора С#?


person Jonathan Wood    schedule 11.03.2015    source источник
comment
Аналогично linq с неназначенными переменными параметрами   -  person SwDevMan81    schedule 11.03.2015
comment
@SwDevMan81: Это совсем не то же самое.   -  person Jonathan Wood    schedule 11.03.2015
comment
Кстати: даже если бы он скомпилировался и сделал бы свое дело --- ваш код все равно содержал бы ошибки. Когда вы выбираете закрытую внешнюю переменную, ее значение одинаково для всех членов результирующего списка. Вы могли бы также select 0. Самым элегантным решением был бы метод расширения TryParse, возвращающий int?   -  person DasKrümelmonster    schedule 11.03.2015
comment
@JonathanWood: На самом деле у них много общего. Тот факт, что определенно назначенный статус проверяется при построении лямбды, а не при ее вызове, является основной причиной обоих.   -  person Ben Voigt    schedule 11.03.2015
comment
@DasKrümelmonster: Вы имеете в виду Parse? Но это вызывает исключение при сбое. Он хочет ноль элементов от каждой ошибки синтаксического анализа и один элемент от каждого успешного синтаксического анализа... что может быть выполнено с помощью SelectMany.   -  person Ben Voigt    schedule 11.03.2015
comment
@BenVoigt: основная проблема может быть той же, но код другой. Тот, кто не знает основной причины, вряд ли увидит, что эти два пункта напрямую связаны, не прочитав сначала много.   -  person Jonathan Wood    schedule 11.03.2015
comment
Можно также написать короткий вспомогательный метод, который возвращает Nullable‹TEnum› и пытается проанализировать его внутри. Затем сделайте что-то вроде: from r in roles let tmp = helpermethod(r) where tmp != null select tmp;   -  person DasKrümelmonster    schedule 11.03.2015
comment
Дополнительное обсуждение этой темы   -  person SwDevMan81    schedule 11.03.2015


Ответы (2)


TL;DR: ошибка говорит о том, что переменная (доказуемо) не назначена -- FALSE. На самом деле, переменная недоказуемо присваивается (используя теоремы доказательства, доступные компилятору).


LINQ разработан с учетом чистых функций... тех, которые возвращают выходные данные на основе входных данных и не имеют побочных эффектов.

Как только это будет переписано, это будет:

roles.Where(r => Enum.TryParse(r, out value)).Select(r => value);

и снова переписал на

Enumerable.Select(Enumerable.Where(roles, r => Enum.TryParse(r, out value)), r => value);

Эти функции LINQ будут вызывать лямбду-фильтр перед любыми вызовами лямбда-выражения, но компилятор не может этого знать (по крайней мере, без анализа потока данных в специальном регистре или между модулями). Более проблематично, если бы для разрешения перегрузки была выбрана другая реализация Where, то, возможно, лямбда с TryParse НЕ вызывалась бы.

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

Вот еще один пример:

bool flag = Blah();
int value;
if (flag) value = 5;
return flag? value: -1;

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

Однако ошибка компилятора сформулирована плохо. Не быть «определенно назначенным» - это не то же самое, что быть определенно «неназначенным», как подразумевается в ошибке.

person Ben Voigt    schedule 11.03.2015

person    schedule
comment
Тогда value тоже не будет оцениваться. - person Ben Voigt; 11.03.2015
comment
Да, что сказал @BenVoigt. Хотя я понимаю, что для компилятора это становится сложной вариацией, это все равно кажется неправильным. - person Jonathan Wood; 11.03.2015
comment
Что делать, если roles — это пустая коллекция? , то есть +1. - person Habib; 11.03.2015
comment
Это оптимизация, которую текущий компилятор не может сделать - другими словами, ошибка, поскольку ошибка технически неверна. - person Jonathan Wood; 11.03.2015
comment
@Habib: Пожалуйста, прочитайте другие комментарии, чтобы узнать, что говорится об этом. - person Jonathan Wood; 11.03.2015
comment
@JonathanWood: Нет, это недостаток в спецификации языка. В компиляторе нет ошибок, поскольку он реализует спецификацию. - person Ben Voigt; 11.03.2015
comment
@BenVoigt: Значит, это ошибка в спецификации? :) - person Jonathan Wood; 11.03.2015
comment
@JonathanWood - это не ошибка, поскольку компилятор не знает, что Where вообще вызовет TryParse. Where - это просто метод расширения и не является особенным. - person Lee; 11.03.2015
comment
@Lee: Но сообщение об ошибке делает утверждение, которое не соответствует действительности. - person Jonathan Wood; 11.03.2015
comment
Нет, код НЕ эквивалентен! В частности, ваша версия совершенно ОК! - person Ben Voigt; 11.03.2015
comment
@JonathanWood - это неправда - Enum.TryParse не гарантируется, что values точно не назначено. - person Lee; 11.03.2015
comment
@JonathanWood, учтите, что int value;var query = new string[] { }.Select(r => int.TryParse(r, out value));Console.WriteLine(value); это все равно приведет к ошибке, связанной с тем, что value не назначено. Так как TryParse вызываться не будет, так как коллекция пуста. - person Habib; 11.03.2015
comment
@Хабиб: Итак? Это совсем другой случай. - person Ben Voigt; 11.03.2015
comment
@JonathanWood: Это недостаток. Не ошибка. Как и тот факт, что компилятор не предупреждает обо всех случаях бесконечных циклов. Но это тоже не ошибка. На самом деле, спецификация, требующая обратного была бы неправильной, поскольку потребовала бы от компилятора решения проблемы остановки. - person Ben Voigt; 11.03.2015
comment
@BenVoigt, int value; var query = new string[] { "1", "2" }.Where(r => int.TryParse(r, out value)).Select(r => value); похож? Компилятор должен следовать определенным правилам назначения и компилятор не может определить, будет ли присвоено значение value или нет. - person Habib; 11.03.2015
comment
@Хабиб: Правильно. Поскольку он не может определить, присвоено ли значение или нет, было бы плохой формулировкой сказать, что переменная точно не присвоена. - person Ben Voigt; 11.03.2015
comment
Плохая формулировка, может быть, но компилятор не может определить с помощью static flow analysis, что значение будет определенно присвоено. - person Habib; 11.03.2015
comment
@Lee: ошибка заключается в том, что использовалось неназначенное значение, хотя на самом деле оно никогда не используется, если оно не назначено. Следовательно, утверждение сообщения об ошибке технически неверно, независимо от причин. - person Jonathan Wood; 11.03.2015
comment
@BenVoigt: Да, это недостаток. Мой опыт был в C и C++, что позволит вам назначать неинициализированные данные в течение всего дня. Теперь я понимаю причины, по которым компилятор не допускает этого, но, тем не менее, он всегда казался мне немного ограничивающим, особенно в таких случаях, как этот, когда мой код действительно вполне действителен. - person Jonathan Wood; 11.03.2015