Почему неназначенные локальные переменные не инициализируются автоматически?

#c# #.net #variables #compiler-construction #initialization

#c# #.net #переменные #построение компилятора #инициализация

Вопрос:

Похоже, что нет никакого способа иметь неназначенные локальные переменные в вашем коде или проверять их, поскольку компилятор выдает Use of unassigned local variable ошибку.

Почему компилятор не использует default(T) эти переменные во время компиляции?

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

Вот некоторый тестовый код:

 public void Test ( )
{
    int x;
    string s;

    if ( x == 5 )
        Console.WriteLine ( 5 );

    if ( s != null )
        Console.WriteLine ( "s" );
}
  

который возвращает:

 Use of unassigned local variable 'x'
Use of unassigned local variable 's'
  

Обновить:

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

 public class C
{
    public int X;
    public string S;

    public void Print ( )
    {
        Console.WriteLine ( X );
        Console.WriteLine ( S );
    }
}
  

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

Почему это нормально иметь на уровне класса, но не на уровне метода?

Комментарии:

1. Компилятор хочет того, чего хочет компилятор.

2. Я думаю, что это дизайнерское решение и ничего более, что-то вроде C / C .

3. Вы бы предпочли иметь ошибку времени компиляции или исключение нулевого указателя (во время выполнения), когда вы забываете инициализировать переменную ссылочного типа?

4. Возможно, компилятор поощряет вас использовать хорошие методы кодирования.

5. Среда CLR фактически инициализирует эту переменную значением по умолчанию (T). Использование значения по умолчанию возможно в vb.net или C / CLI. Но не в C #, разработчики языка решили сделать это ошибкой «очень вероятно, что это ошибка». Как бы то ни было, многие программисты на C и C сильно пострадали от этого.

Ответ №1:

Я вижу, вы обновили свой вопрос, поэтому я обновлю свой ответ. Ваш вопрос состоит из двух частей, одна из которых относится к local variables , а другая к instance variables on a class instance . Во-первых, однако, это на самом деле не решение о разработке компилятора, а скорее решение о разработке языка.

Раздел спецификации 12.3.1 / 12.3.2

Локальные переменные

Мы знаем, почему вы можете определить переменную, не присваивая ей значения. Одна из причин, пример чего-то подобного:

 int x;
// do stuff
x = 5;  // Wow, I can initialize it later!
Console.WriteLine(x);
  

Стандарт определяет, почему это допустимый код. Я не из команды разработчиков C #, но вполне понятно, почему они не будут автоматически инициализировать код для вас (помимо снижения производительности, когда вы на самом деле не хотели, чтобы он автоматически инициализировался).

Допустим, приведенный выше код был вашим намерением, но вы забыли инициализировать x = 5; . Если компилятор автоматически инициализировал переменную для вас, код будет компилироваться, но он не будет делать ничего такого, чего вы ожидаете.

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

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

Переменные экземпляра класса

Члены уровня класса определяются стандартом, который должен быть назначен изначально. На самом деле, справедливости ради, локальные переменные за пределами тех, которые объявлены в catch foreach using операторе или , изначально неназначены. Так что на самом деле это проблема стандартов, а не проблема компилятора.

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

Комментарии:

1. Спасибо, я добавил больше деталей для решения этой проблемы. Почему это нормально иметь это на уровне класса? Если я забуду инициализировать те же переменные, я все равно увижу неожиданное поведение?

2. @JoanVenge — Я добавил ответ на вашу правку. Конечно, это все еще предположение, но я думаю, что оно, вероятно, действительно.

Ответ №2:

C # — это язык «ямы успеха».

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

Комментарии:

1. Переменные, не входящие в стек, гарантированно инициализируются любым двоичным нулем, представленным для данного типа (обычно 0, null, …).

Ответ №3:

1 Почему компилятор не разрешает использование неинициализированных переменных?

Потому что предотвращение этого способствует хорошему программированию.

2 Почему компилятор разрешает использование неинициализированных членов класса?

Потому что невозможно отследить это с какой-либо точностью.

Ответ №4:

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

Комментарии:

1. Если вы не инициализировали переменную и знаете, что компилятор инициализирует ее значением null для вас, то у вас не будет исключения null ref во время выполнения чаще, чем если вы инициализируете его значением null, а затем забудете его проверить.

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

3. @JoanVenge Вы, кажется, предполагаете, что никогда не совершите ошибку. Поскольку я человек и делаю ошибки, я хотел бы узнать о них во время компиляции, а не во время выполнения.

4. @CraigStuntz: Я добавил больше деталей к вопросу. Я не предполагаю, что не делаю ошибок, но почему это разрешено на уровне класса?

5. @JoanVenge «подробнее» полностью меняет вопрос. На уровне класса рассуждения другие. На уровне класса все поля должны назначаться при запуске конструкторов, иначе вы не сможете ссылаться на поля в конструкторе. Отличие от таких языков, как C , которые не допускают использования виртуальных членов в конструкторах, или Scala, который предоставляет способы назначения членов предсказуемым образом.

Ответ №5:

Рассмотрим следующий код:

 void blah(IDictionary<int,int> dict)
{
  for (int i=0; i<10; i  )
  {
    if ((i amp; 11) != 0)
    {
        int j;
        dict.TryGetValue(i, out j);
        System.Diagnostics.Debug.Print("{0}",j);
        j  ;
    }
  }
}
  

Предположим, что TryGetValue метод переданной реализации IDictionary<int,int> never фактически выполняет запись в j [невозможно, если он написан на C #, но возможно, если он написан на другом языке]. Что следует ожидать от кода для печати?

Ничто в стандарте C # не требует, чтобы j это поддерживалось, когда код покидает if оператор, но ничто не требует, чтобы он сбрасывался на ноль между итерациями цикла. В некоторых случаях обязательное выполнение любого из этих действий повлечет за собой дополнительные расходы. Вместо того, чтобы делать либо то, либо другое, стандарт просто допускает, что при TryGetValue вызове j может произвольно содержать ноль или последнее значение, которое он удерживал, когда оно было в области видимости. Такой подход позволяет избежать ненужных затрат, но было бы неудобно, если бы коду было разрешено видеть значение j между временем, когда он повторно входит в область видимости, и временем его записи (тот факт, что передача неинициализированной переменной в качестве out параметра коду, написанному на другом языке, покажет ее значение, вероятно, был непреднамеренным).

Ответ №6:

Потому что чего ты там хочешь? вы хотите, чтобы x по умолчанию было равно нулю, а я хочу, чтобы оно было 5…

если они присваивают 0 int (s) и весь мир начинает предполагать это, то в какой-то момент они изменятся на -1, и это приведет к поломке очень многих приложений по всему миру.

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

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

Комментарии:

1. Хорошо… за исключением того, что переменные, не входящие в стек, гарантированно инициализируются нулем или нулем. Язык определяет это таким образом, поэтому он не изменится в будущем.

2. Я понимаю, что вы имеете в виду, но почему вы не жалуетесь на то, что такое поведение существует на уровне класса? Также, если они используют 0 по умолчанию, позже они не смогут изменить его на -1. По той же причине, по которой они не меняют многое из того, что делали раньше, из-за проблем со сбоями.