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

Неожиданная ошибка времени компиляции с динамическим

Уточнение вопроса: Я не ищу ответы на вопрос о том, как решить эту проблему (некоторые из них перечислены ниже), но почему это происходит.

Я ожидаю, что следующий код будет скомпилирован:

struct Alice
{
    public string Alpha;
    public string Beta;
}

struct Bob
{
    public long Gamma;
}

static object Foo(dynamic alice)
{
    decimal alpha;
    long beta;

    if (!decimal.TryParse(alice.Alpha, out alpha) // *
        || !long.TryParse(alice.Beta, out beta)) // **
    {
        return alice;
    }

    var bob = new Bob { Gamma = beta }; // ***

    // do some stuff with alice and bob

    return alice;
}

Однако следующая ошибка времени компиляции генерируется при // ***:

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

Я могу скомпилировать программу в следующих ситуациях:

  • Если я изменяю подпись

    static object Foo(Alice alice)

  • Явное литье на строках // * и // **, например:

    !long.TryParse((string)alice.Beta, out beta).

  • Удаление decimal.TryParse в строке // *.

  • Замена короткого замыкания или || на |. Благодаря HansPassant

  • Перемещение TryParse вокруг

  • Вытягивание результатов TryParse в bool Благодаря Chris

  • Назначение значения по умолчанию beta

Я пропустил что-то очевидное, или есть что-то тонкое, или это ошибка?

4b9b3361

Ответ 1

Я точно не знаю ответа, но для меня это похоже на ошибку компилятора или "по дизайну".

Я немного играл с вашим образцом, уменьшая его пополам, и вот что осталось от него:

    private static bool Default<T>(out T result)
    {
        result = default(T);
        return true;
    }

    private static void Foo()
    {
        int result;

        if (true || Default(out result))
        {
            return;
        }

        Console.WriteLine(result);
    }

Что также не удается выполнить с помощью

ошибка CS0165: использование не назначенной локальной переменной 'result'

Вы можете играть с int result в Foo, чтобы проверить любой тип, который вы хотите.

Обратите внимание, что нет использования dynamic, а также обратите внимание на ветку true, которая должна немедленно вернуться.

Итак, для меня это похоже, что компилятор VS.Net здесь "недостаточно интеллектуальный".

Что хорошо с этим фрагментом кода - его можно скомпилировать с компиляторами до .Net 4 (используя csc.exe из соответствующих фреймворков), так что вот результаты:

  • .Net 2.0

Сборка в порядке, предупреждения:

предупреждение CS0429: обнаружен недостижимый код выражения

     

предупреждение CS0162: обнаружен недостижимый код

  • .Net 3.5

Ошибка сборки:

ошибка CS0165: использование не назначенной локальной переменной 'result'

Итак, если это ошибка, она появляется где-то между .NET 2 и .NET 3.5

Ответ 2

Это происходит потому, что ключевое слово dynamic вызывает множество изменений в сгенерированной структуре кода (сгенерировано компилятором С#).

Вы можете наблюдать это с помощью такого инструмента, как рефлектор .NET(я предлагаю выбрать "Нет" для вывода С#, чтобы вы могли действительно увидеть все сгенерированные вещи). В принципе, каждый раз, когда вы обращаетесь к объекту dynamic, сгенерированный код добавляет хотя бы случай if. Эти ifs могут привести к изменениям важных путей кода.

Например, этот простой код С#

    static void MyFoo(dynamic dyn)
    {
        if (dyn == null)
            return;

        var x = dyn;
    }

генерируется как:

private static void MyFoo([Dynamic] object dyn)
{
    object obj2;
    CSharpArgumentInfo[] infoArray;
    bool flag;
    if (<MyFoo>o__SiteContainer0.<>p__Site1 != null)
    {
        goto Label_0038;
    }
    <MyFoo>o__SiteContainer0.<>p__Site1 = CallSite<Func<CallSite, object, bool>>.Create(Binder.UnaryOperation(0, 0x53, typeof(Program), new CSharpArgumentInfo[] { CSharpArgumentInfo.Create(0, null) }));
Label_0038:
    if (<MyFoo>o__SiteContainer0.<>p__Site2 != null)
    {
        goto Label_0088;
    }
    <MyFoo>o__SiteContainer0.<>p__Site2 = CallSite<Func<CallSite, object, object, object>>.Create(Binder.BinaryOperation(0, 13, typeof(Program), new CSharpArgumentInfo[] { CSharpArgumentInfo.Create(0, null), CSharpArgumentInfo.Create(2, null) }));
Label_0088:
    if ((<MyFoo>o__SiteContainer0.<>p__Site1.Target(<MyFoo>o__SiteContainer0.<>p__Site1, <MyFoo>o__SiteContainer0.<>p__Site2.Target(<MyFoo>o__SiteContainer0.<>p__Site2, dyn, null)) == 0) == null)
    {
        goto Label_00AE;
    }
    obj2 = dyn;
Label_00AE:
    return;
}

Возьмем еще один простой пример. Этот код:

    static void MyFoo1(dynamic dyn)
    {
        long value;

        if (long.TryParse(dyn, out value))
            return;

        var x = value;
    }

компилируется отлично. Этот

    static void MyFoo2(dynamic dyn)
    {
        long value;

        if (true || long.TryParse(dyn, out value))
            return;

        var x = value;
    }

нет. Если вы посмотрите на сгенерированный код (задайте значение 0, чтобы убедиться, что он скомпилирован), он просто будет содержать некоторые дополнительные ifs и goto, которые существенно изменят весь код. Я не думаю, что это ошибка, возможно, более ограниченное ключевое слово dynamic (у которого довольно много, например: Ограничения динамического типа на С#)

Ответ 3

Это просто для || использование оператора (логическое ИЛИ),

компилятор проверяет критерии слева направо, поэтому, если первый операнд оценивается как ИСТИНА, второй не нужно оценивать, поэтому Бета не может получить значение, а компилятор выдает следующее предупреждение:

"Use of unassigned local variable 'beta'"

Ваш код:

    if (
        !decimal.TryParse(alice.Alpha, out alpha)  // If evaluated as TRUE...
        ||                                         // 
        !long.TryParse(alice.Beta, out beta))      // ....so Will not evaluated this.
{

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

"Use of unassigned local variable 'alpha'"

Ответ 4

Присвоить значения по умолчанию бета и альфа при объявлении

decimal alpha=default(decimal);
long beta =default(long); 

Ответ 5

Хорошо, вот моя попытка:

Когда alice имеет тип 'alice', компилятор знает во время компиляции, который будет вызываться метод TryParse, и он знает, что метод имеет параметр 'out', что означает его будет инициализирован методом. Итак, в случае, если alice имеет тип alice, либо decimal.TryParse вернет false, и в этом случае Bob не будет сконструирован (потому что метод возвращает alice), или decimal.TryParse вернет true, а в этот случай long.TryParse будет вызываться, а компилятор знает, что это назначит значение beta (из-за ключевого слова out).

Теперь, если alice имеет тип dynamic, компилятор не заботится о сигнатуре метода TryParse и откладывает разрешение метода (на основе фактического типа alice) в время выполнения. Это означает, что он не может делать никаких предположений о том, что beta будет присвоено значение или нет вызовом TryParse(), и поэтому не может быть уверен, что beta будет присвоено значение при использовании для конструктора Боба.

Мои 2 цента...

Ответ 6

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

это оценивается все время.. но так как это условие или условие. Когда первое выражение истинно, оно прерывается от if, не оценивая второе.

! decimal.TryParse(alice.Alpha, out alpha)

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

! decimal.TryParse(alice.Alpha, out alpha) ! long.TryParse(alice.Alpha, out beta);

здесь гарантируется, что некоторое значение будет присвоено бета-версии. Надеюсь, я сказал.

Ответ 7

Кажется, что даже у выдающихся людей есть проблемы, чтобы решить, является ли это ошибкой компилятора или по дизайну, как можно увидеть здесь, посмотрев на два ответа Энтони Д. Грина, менеджера программ, Visual Basic и С# Language Team. Этот отчет об ошибке в основном имеет такую ​​же проблему, как описано здесь, а именно использование динамического значения, которое может привести к CS0165 при использовании другого типа, решает его. Также расщепление && Операция с двумя операторами if вместо этого решила проблему в этом отчете об ошибке.

В ответе г-на Грина есть ссылка на этот вопрос, который, кажется, является дубликатом этого. Тайна за всем этим хорошо объясняется в этом ответе, а также г-н Грин в отчете об ошибке, приведенном выше. Проблема в том, что значение динамического типа может иметь перегруженный оператор true, который вызывается ||, а оператор if и собственная реализация могут делать что угодно, поэтому компилятор осторожно относится к этому материалу. Связанная информация дает лучшее объяснение, хотя:)

Ответ 8

Я предполагаю, что это происходит потому, что в вашем текущем коде с включенным dynamic алгоритм, используемый компилятором для определения того, действительно ли TryParse на время выполнения, является недетерминированным. Я думаю, что решения, которые вы видите, избегают этой проблемы, заставляя оценку этих вызовов TryParse стать детерминированными (т.е. Они будут оцениваться). Когда эти вызовы TryParse оцениваются с помощью параметра out, они в худшем случае получат значение default(decimal) и default(long). Поэтому, когда вы назначаете его позже, значение не назначается. Это предположение, основанное на прыжке goto, которое появляется в сгенерированном коде

Это немного поддерживается документацией Int32.TryParse

Когда этот метод возвращает, содержит 32-разрядное целое число со знаком, эквивалентное числу, содержащемуся в s, если преобразование преуспело, или ноль, если преобразование завершилось неудачно. Преобразование завершается с ошибкой, если параметр s равен null или String.Empty, не соответствует правильному формату или представляет собой число меньше MinValue или больше MaxValue. Этот параметр передается неинициализированным.

Это определенно основано на выводах Саймона Мурье в его ответе.

Ответ 9

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

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

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

Ответ 10

long beta;

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

long beta = new long();