Чому це (null ||! TryParse) умовно призводить до "використання непризначеної локальної змінної"?


98

Наступний код призводить до використання непризначеної локальної змінної "numberOfGroups" :

int numberOfGroups;
if(options.NumberOfGroups == null || !int.TryParse(options.NumberOfGroups, out numberOfGroups))
{
    numberOfGroups = 10;
}

Однак цей код працює нормально (хоча, ReSharper каже, що = 10це зайвий):

int numberOfGroups = 10;
if(options.NumberOfGroups == null || !int.TryParse(options.NumberOfGroups, out numberOfGroups))
{
    numberOfGroups = 10;
}

Мені щось не вистачає, або компілятор мені не подобається ||?

Я звузив це до dynamicвиникнення проблем (це optionsбула динамічна змінна у моєму вище коді). Все ще залишається питання, чому я не можу цього зробити ?

Цей код не компілюється:

internal class Program
{
    #region Static Methods

    private static void Main(string[] args)
    {
        dynamic myString = args[0];

        int myInt;
        if(myString == null || !int.TryParse(myString, out myInt))
        {
            myInt = 10;
        }

        Console.WriteLine(myInt);
    }

    #endregion
}

Однак цей код робить :

internal class Program
{
    #region Static Methods

    private static void Main(string[] args)
    {
        var myString = args[0]; // var would be string

        int myInt;
        if(myString == null || !int.TryParse(myString, out myInt))
        {
            myInt = 10;
        }

        Console.WriteLine(myInt);
    }

    #endregion
}

Я не підозрював, що dynamicце буде фактором у цьому.


Не думайте, що це досить розумно, щоб знати, що ви не використовуєте значення, передане в ваш outпараметр, як вхід
Шарле

3
Наведений тут код не демонструє описаної поведінки; це працює чудово. Будь ласка, надішліть код, який фактично демонструє поведінку, яку ви описуєте, яку ми можемо скласти самі. Дайте нам весь файл.
Ерік Ліпперт,

8
Ах, тепер у нас є щось цікаве!
Ерік Ліпперт

1
Не надто дивно, що компілятор це бентежить. Допоміжний код для сайту динамічного виклику, ймовірно, має деякий потік керування, який не гарантує призначення outпараметру. Безумовно, цікаво розглянути, який допоміжний код повинен створити компілятор, щоб уникнути проблеми, або якщо це навіть можливо.
CodesInChaos

1
На перший погляд це впевнено схоже на помилку.
Ерік Ліпперт,

Відповіді:


73

Я майже впевнений, що це помилка компілятора. Приємна знахідка!

Редагувати: це не помилка, як демонструє Quartermeister; Dynamic може реалізувати дивний trueоператор, який може призвести yдо того, що він ніколи не ініціалізується.

Ось мінімальний репро:

class Program
{
    static bool M(out int x) 
    { 
        x = 123; 
        return true; 
    }
    static int N(dynamic d)
    {
        int y;
        if(d || M(out y))
            y = 10;
        return y; 
    }
}

Я не бачу причин, чому це повинно бути незаконним; якщо замінити динамічний на bool, він компілюється просто чудово.

Я фактично зустрічаюся з командою C # завтра; Я їм про це згадаю. Вибачення за помилку!


6
Мені просто приємно знати, що я не збожеволіла :) З тих пір я оновила свій код, щоб він покладався лише на TryParse, тож я зараз готовий. Дякуємо за розуміння!
Брендон Мартінес

4
@NominSim: Припустимо, що аналіз середовища виконання не вдається: тоді створюється виняток перед читанням локальної програми. Припустимо, аналіз виконання вдався: тоді під час виконання або d є істинним, і y встановлено, або d - хибне, а M встановлює y. У будь-якому випадку, y встановлено. Той факт, що аналіз відкладено до виконання, нічого не змінює.
Ерік Ліпперт

2
На випадок, якщо комусь цікаво: я щойно перевірив, і компілятор Mono це правильно зрозумів. imgur.com/g47oquT
Дан Тао

17
Я думаю, що поведінка компілятора насправді є правильною, оскільки значення dможе мати тип із перевантаженим trueоператором. Я опублікував відповідь із прикладом, коли не береться жодна гілка.
Quartermeister

2
@Quartermeister, в цьому випадку компілятор Mono помиляється :)
porges

52

Неможливо призначити змінну, якщо значення динамічного виразу має тип із перевантаженим trueоператором .

||Оператор буде викликати trueоператор , щоб вирішити , слід оцінювати праву, а потім ifоператор буде викликати trueоператор , щоб вирішити , слід оцінювати своє тіло. У звичайному випадку boolвони завжди повертатимуть однаковий результат, і тому буде оцінено саме один, але для визначеного користувачем оператора така гарантія відсутня!

Опираючись на доказ Еріка Ліпперта, ось коротка і повна програма, яка демонструє випадок, коли жоден шлях не буде виконаний, а змінна матиме початкове значення:

using System;

class Program
{
    static bool M(out int x)
    {
        x = 123;
        return true;
    }

    static int N(dynamic d)
    {
        int y = 3;
        if (d || M(out y))
            y = 10;
        return y;
    }

    static void Main(string[] args)
    {
        var result = N(new EvilBool());
        // Prints 3!
        Console.WriteLine(result);
    }
}

class EvilBool
{
    private bool value;

    public static bool operator true(EvilBool b)
    {
        // Return true the first time this is called
        // and false the second time
        b.value = !b.value;
        return b.value;
    }

    public static bool operator false(EvilBool b)
    {
        throw new NotImplementedException();
    }
}

8
Приємна робота тут. Я передав це команді тестування та дизайну C #; Побачу, чи вони мають якісь коментарі щодо цього, коли побачу їх завтра.
Ерік Ліпперт

3
Це мені дуже дивно. Чому слід dоцінювати двічі? (Я не сперечаюся , що вона ясно це , як ви показали.) Я очікував би оціночний результату true(від першого оператора виклику, причини по ||) , щоб бути «пройшов уздовж» в ifзаяву. Це, звичайно, станеться, якщо ви помістите туди виклик функції, наприклад.
Дан Тао

3
@DanTao: Вираз dоцінюється лише один раз, як ви очікуєте. Це trueоператор, який викликається двічі, один раз ||і один раз if.
Quartermeister

2
@DanTao: Може бути зрозумілішим, якщо ми розмістимо їх у окремих висловлюваннях як var cond = d || M(out y); if (cond) { ... }. Спочатку ми обчислюємо, dщоб отримати EvilBoolпосилання на об’єкт. Щоб оцінити ||, спочатку ми використовуємо EvilBool.trueз цим посиланням. Це повертає true, тому ми робимо коротке замикання і не викликаємо M, а потім призначаємо посилання на cond. Потім ми переходимо до ifтвердження. ifОператор оцінює його стан шляхом виклику EvilBool.true.
Quartermeister

2
Зараз це справді круто. Я навіть не підозрював, що існує оператор true або false.
IllidanS4 хоче повернути Моніку

7

З MSDN (курсив мій):

Динамічний тип дозволяє операціям, у яких виникає, обійти перевірку типу компіляції . Натомість ці операції вирішуються під час виконання . Динамічний тип спрощує доступ до COM API, таких як API автоматизації Office, а також до динамічних API, таких як бібліотеки IronPython, та до об'єктної моделі документа HTML (DOM).

Динамічний тип в більшості випадків поводиться як об’єкт типу. Однак операції, що містять вирази типу dynamic, не вирішуються або перевіряються типом компілятором.

Оскільки компілятор не вводить перевірку типу та не вирішує будь-які операції, що містять вирази типу dynamic, він не може гарантувати, що змінній буде призначено за допомогою TryParse().


Якщо перша умова виконана, numberGroupsприсвоюється (у if trueблоці), якщо ні, друга умова гарантує призначення (через out).
леппі

1
Це цікава думка, але код добре компілюється без myString == null(покладаючись лише на TryParse).
Брендон Мартінес

1
@leppie Справа в тому, що оскільки перша умова (а отже, і весь ifвираз) включає dynamicзмінну, вона не вирішується під час компіляції (тому компілятор не може робити ці припущення).
NominSim

@NominSim: Я бачу вашу думку :) +1 Можливо, жертва від компілятора (порушення правил C #), але інші пропозиції, мабуть, передбачають помилку. Фрагмент Еріка показує, що це не жертва, а помилка.
леппі

@NominSim Це не може бути правильно; те, що певні функції компілятора відкладено, не означає, що всі вони є такими. Існує безліч доказів того, що за дещо інших обставин компілятор виконує певний аналіз призначення без проблем, незважаючи на наявність динамічного виразу.
dlev
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.