Відповіді Юваля та Девіда в основному правильні; Підводячи підсумки:
- Використання непризначеної локальної змінної - ймовірна помилка, і це може виявити компілятор за низькою вартістю.
- Використання непризначеного елемента поля чи масиву є меншою ймовірністю помилки, і складніше виявити стан у компіляторі. Тому компілятор не робить спроб виявити використання неініціалізованої змінної для полів, а замість цього покладається на ініціалізацію до значення за замовчуванням, щоб зробити поведінку програми детермінованою.
Учасник відповіді Девіда запитує, чому неможливо виявити використання непризначеного поля за допомогою статичного аналізу; це я хочу розгорнути у цій відповіді.
По-перше, для будь-якої змінної, локальної чи іншої, на практиці неможливо точно визначити, присвоєна чи непризначена змінна. Поміркуйте:
bool x;
if (M()) x = true;
Console.WriteLine(x);
Питання "призначено х?" еквівалентно "чи повертає М () істину?" Тепер, припустимо, M () повертає істину, якщо Остання теорема Ферма відповідає правилам для всіх цілих чисел, менших за одинадцять гаджліонів, а помилка - інакше. Для того, щоб визначити, чи точно визначено х, компілятор повинен по суті створити доказ останньої теореми Ферма. Компілятор не такий розумний.
Отже, те, що компілятор робить замість місцевих жителів, реалізує швидкий алгоритм і переоцінює, коли локальний не визначено визначено. Тобто у неї є помилкові позитиви, де написано "я не можу довести, що цей локальний присвоєний", навіть якщо ви і я знаю, що це так. Наприклад:
bool x;
if (N() * 0 == 0) x = true;
Console.WriteLine(x);
Припустимо, N () повертає ціле число. Ви і я знаємо, що N () * 0 буде 0, але компілятор цього не знає. (Примітка: компілятор C # 2.0 це знав, але я зняв цю оптимізацію, оскільки специфікація не говорить про те, що компілятор це знає.)
Гаразд, так що ми знаємо поки що? Місцеві жителі недоцільно отримувати точну відповідь, але ми можемо недооцінити недосвідченість дешево і отримати досить хороший результат, який помиляється на стороні "змусить виправити свою незрозумілу програму". Добре. Чому б не зробити те ж саме для полів? Тобто зробити певну перевірку завдань, яка завищує недорого?
Ну, скільки існує способів ініціалізації локального? Її можна призначити в тексті методу. Його можна призначити в межах лямбда в тексті методу; що лямбда ніколи не може бути використана, тому ці призначення не є актуальними. Або він може бути переданий як "поза" іншому методу, і тоді ми можемо вважати, що він призначений, коли метод повертається нормально. Це дуже чіткі моменти, за якими призначається локальний, і вони знаходяться там саме тим же методом, що і місцевий оголошений . Визначення певного призначення місцевим жителям вимагає лише місцевого аналізу . Методи, як правило, короткі - набагато менше мільйона рядків коду в методі - і тому аналіз всього методу досить швидкий.
А що робити з полями? Поля можуть бути ініціалізовані в конструкторі звичайно. Або польовий ініціалізатор. Або конструктор може викликати метод екземпляра, який ініціалізує поля. Або конструктор може викликати віртуальний метод, який ініціалізує поля. Або конструктор може викликати метод в іншому класі , який може бути в бібліотеці , який ініціалізує поля. Статичні поля можуть бути ініціалізовані в статичних конструкторах. Статичні поля можуть бути ініціалізовані іншими статичними конструкторами.
По суті, ініціалізатор поля може бути в будь-якій точці всієї програми , включаючи всередині віртуальних методів, які будуть оголошені в бібліотеках, які ще не були записані :
// Library written by BarCorp
public abstract class Bar
{
// Derived class is responsible for initializing x.
protected int x;
protected abstract void InitializeX();
public void M()
{
InitializeX();
Console.WriteLine(x);
}
}
Чи помилка при складанні цієї бібліотеки? Якщо так, як BarCorp повинен виправити помилку? Призначивши значення x за замовчуванням до x? Але це вже робить компілятор.
Припустимо, ця бібліотека легальна. Якщо FooCorp пише
public class Foo : Bar
{
protected override void InitializeX() { }
}
це що помилка? Як компілятор повинен це зрозуміти? Єдиний спосіб - зробити цілий аналіз програми, який відстежує статику ініціалізації кожного поля на кожному можливому шляху через програму , включаючи шляхи, що передбачають вибір віртуальних методів під час виконання . Ця проблема може бути довільно важкою ; це може включати модельоване виконання мільйонів контурів управління. Аналіз локальних потоків управління займає мікросекунди і залежить від розміру методу. Аналіз глобальних потоків управління може зайняти години, оскільки це залежить від складності кожного методу в програмі та всіх бібліотек .
То чому б не зробити більш дешевий аналіз, який не повинен аналізувати всю програму, а просто завищувати ще сильніше? Що ж, запропонуйте алгоритм, який працює, і це не дуже важко написати правильну програму, яка насправді компілює, і команда дизайнерів може це врахувати. Я не знаю жодного такого алгоритму.
Тепер, коментатор пропонує "вимагати, щоб конструктор ініціалізував усі поля". Це не погана ідея. Насправді, настільки непогана ідея, що C # вже має таку особливість для структур . Конструктор структури повинен обов'язково призначити всі поля до моменту нормального повернення ctor; конструктор за замовчуванням ініціалізує всі поля до їх значень за замовчуванням.
А як щодо занять? Ну як ви знаєте, що конструктор ініціалізував поле ? Ctor міг викликати віртуальний метод для ініціалізації полів, і тепер ми знову в тому ж становищі, в якому були раніше. У структур немає похідних класів; класи можуть. Чи потрібна бібліотека, що містить абстрактний клас, щоб містити конструктор, який ініціалізує всі його поля? Як абстрактний клас знає, до яких значень поля слід ініціалізувати?
Джон пропонує просто заборонити викликувати методи виклику в ctor перед ініціалізацією полів. Отже, підводячи підсумки, наші варіанти:
- Зробити загальні, безпечні, часто використовувані ідіоми програмування незаконними.
- Зробіть дорогий аналіз всієї програми, що змушує компіляцію займати години, щоб шукати помилок, яких, мабуть, немає.
- Покладайтеся на автоматичну ініціалізацію до значень за замовчуванням.
Команда дизайнерів обрала третій варіант.