Почему выдается ошибка компилятора CS0165: использование неназначенной локальной переменной? - PullRequest
4 голосов
/ 26 июня 2019

Я поместил приведенный ниже код, а также загрузил его в онлайн-компилятор c #: jdoodle.com / a / 1jww код может компилироваться и запускаться в режиме онлайн, однако он не компилируется в моем локальном визуалеstudio.

Я использую:

Visual Studio 2017 15.9.13,

Консольное приложение, .Net Framework 4.7.2

Языкверсия c # 7.3

Компилятор Microsoft (R) Visual C # версии 2.10.0.0 (b9fb1610)

Ниже приведен код:

class Program
{
    static void Main()
    {
        Dictionary<string,int> myDict = new Dictionary<string,int>();
        myDict.Add("hello", 1);

        if (myDict?.TryGetValue("hello", out var value) == true)
        {               
            Console.WriteLine("Hello" + value.ToString());
        }
    }
}

, ожидающий увидеть Hello1in Console.Output Поскольку, если условие истинно, проверка Null-Conditional должна вернуть ненулевое значение, а ключ существует в словаре, а значение должно быть назначено при возврате из метода TryGetValue.

Поскольку согласно документации :

вызываемый метод должен присвоить значение до его возврата.


обновление: это открытый вопрос в https://github.com/dotnet/roslyn/issues/32572
Я думаю, что это настоящая проблема / буг, если требование к компилятору включает в себя не давать ложную тревогу.Мои аргументы:Всякий раз, когда ЦП выполняет до точки внутри блока кода скобки if, значение должно возвращаться из вызова TryGetValue и НЕ является «неназначенной локальной переменной».Более простое решение - дать что-то вроде «невозможно определить статус назначения» как предупреждение вместо ошибки, если компилятор не может ожидать статус назначения при интерпретации нулевого условного оператора.

Ответы [ 4 ]

3 голосов
/ 26 июня 2019

Это из-за разницы в компиляторе.

В этой скрипте https://dotnetfiddle.net/5GgGNS, вы можете увидеть ошибку, которая пропущена в моно-компиляторе.

Я думаю, что ошибкадопустимо в связи с тем, что эта строка

if (myDict?.TryGetValue("hello", out var value) == true)

не гарантирует инициализацию локальной переменной value.

Если вы переписали бы ее в:

if (myDict?.TryGetValue("hello", out var value) == null)

он попытается получить доступ к value.

Теперь значение null или true в вашем случае может быть возвращаемым значением функции, которое будет известно только во время выполнения.

Но, поскольку все переменные в основном всегда инициализируются , это просто функция компилятора.

С другой стороны, согласно спецификациям C # 5:

Локальная переменная, представленная объявлением локальной переменной, не инициализируется автоматически и поэтому не имеет значения по умолчанию.В целях проверки определенного присваивания локальная переменная, введенная объявлением локальной переменной, изначально считается неназначенной.Объявление локальной переменной может включать инициализатор локальной переменной, и в этом случае переменная считается определенно назначенной только после инициализирующего выражения (§5.3.3.4).

Но ваш код - C #6.

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


Аргументация

Забавный факт, если вы используете этот код:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

class Program
{
    static void Main()
    {
        //Your code goes here
        Dictionary<string,int> myDict = null;

        if (myDict?.TryGetValue("hello", out var value) == null)
        {               
            Console.WriteLine("Hello" + value.ToString());
        }
    }
}

[использование https://www.jdoodle.com/compile-c-sharp-online, моно 5.10.1]

Вы увидите фактическую инициализацию для default(T) на работе.Выход Hello0.Тем не менее, это замечательно, потому что из-за ? и того факта, что myDict - это null, TryGetValue не следует вызывать и оставить value "неинициализированным" .

Нулевые операторы имеют короткое замыкание.То есть, если одна операция в цепочке условных элементов или операций доступа к элементу возвращает ноль, остальная часть цепочки не выполняется.

source

Но ... , поскольку нет неинициализированных переменных;если он скомпилируется, компилятор удостоверится, что его поведение не определено.


Итак, поскольку value инициализируется , во время выполнения остается вопрос, является ли этодопустимая ошибка компилятора во время сборки.Что касается времени выполнения кода, то оно есть (и именно поэтому ошибка была там, во-первых), но я думаю, что она остается серой областью.

Обратите внимание, что согласно это default(T) не может быть переопределено, что фактически приведет к тому, что не произойдет сбой.


При выполнении этого небольшого теста:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

class Program
{
    static void Main()
    {
        //Your code goes here
        Dictionary<string,int> myDict = null;

        if (myDict?.Bar(out var test) == null)
        {               
            Console.WriteLine("does hit");
        }
    }
}

static class Foo
{
    public static object Bar(this Dictionary<string,int> input, out int test)
    {
        test = 3;
        Console.WriteLine("does not hit");
        return 1;
    }
}

[с использованием https://www.jdoodle.com/compile-c-sharp-online, моно 5.10.1]

Вывод становится:

does hit

И вы можете проверить правильное поведение во время выполнения оператора ?..

2 голосов
/ 26 июня 2019

Условное значение NULL ?. снимает гарантию того, что value будет назначено, поскольку TryGetValue будет вызываться только условно, если myDict не null.

Вы принудительно назначаете присваивание value внутри оператора if с помощью == true, поскольку левая сторона вернет null, если TryGetValue не вызывается, поскольку myDict сам по себе равен нулю. Однако компилятор не может сделать этот вывод в общем случае, поэтому вы должны помочь ему, либо предварительно протестировав myDict для null (и пропустив ?.) или , инициализируя value заранее.

0 голосов
/ 27 июня 2019

Нулевой условный оператор вводит другой путь выполнения, где он замыкает накоротко TryGetValue. Однако переменная value существует в любом случае, поскольку она выходит за пределы операторов if и TryGetValue, что указывает на то, что этот вид синтаксического сахара должен привести к старому доброму объявлению переменной до ее использования. Например:

if (myDict?.TryGetValue("hello", out var value) == true)
{
    Console.WriteLine("Hello" + value.ToString());
}        
//Current issue aside, this is legal as the variable exists at a higher scope, 
//beyond the if and TryGetValue statements although the brackets give the illusion of scope.
value = 2;

Должен быть эквивалентен:

int value;
if (myDict?.TryGetValue("hello", out value) == true)
{
    Console.WriteLine("Hello" + value.ToString());
}           
value = 2;

Что также приводит к ошибке компиляции.

Однако такая же ошибка будет выдана для myDict, если это будет null, и вы не используете нулевой условный оператор:

Dictionary<string, int> myDict; //Not initialized        
//You would get the warning for myDict here instead
if (myDict.TryGetValue("hello", out var value) == true)
{
    Console.WriteLine("Hello" + value.ToString());
}

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

При этом документация гласит:

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

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

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

0 голосов
/ 26 июня 2019

Я бы предложил инициализировать переменную-значение перед вашим вызовом TryGetValue.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...