2008-10-08 5 views
15

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

static private List<int> a = new List<int>() { 0 }; 
static private List<int> b = new List<int>() { a[0] }; 

Не делая ничего особенного, что работал. Это просто удача? Есть ли у C# правила для решения этой проблемы?

Редактировать: (re: Panos) В файле лексический порядок кажется королем? как насчет файлов?

В поисках я попробовал циклическую зависимость, как это:

static private List<int> a = new List<int>() { b[0] }; 
static private List<int> b = new List<int>() { a[0] }; 

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

+0

Я думаю, что через файлы (т. Е. Через разные классы) это произойдет одинаково. Во время инициализации типа класса A будет предложено инициализировать класс B, а класс B найдет нулевую ссылку на класс A. – Panos 2008-10-09 00:06:24

+0

Теперь, в файлах того же класса (частичный класс), вероятно, зависит от препроцессора до определить, не терпит неудачу или нет. – Panos 2008-10-09 00:07:44

+0

Итак, если A ссылается на B.b, тогда A.a получает доступ к buck B.b? – BCS 2008-10-09 00:09:22

ответ

13

Кажется, что это зависит от последовательности линий. Этот код работает:

static private List<int> a = new List<int>() { 1 }; 
static private List<int> b = new List<int>() { a[0] }; 

пока этот код не работает (она бросает NullReferenceException)

static private List<int> a = new List<int>() { b[0] }; 
static private List<int> b = new List<int>() { 1 }; 

Таким образом, очевидно, никаких правил для циклической зависимости не существует. Специфично, однако, что компилятор не жалуется ...


EDIT - Что происходит с «файлами»? Если объявить эти два класса:

public class A { 
    public static List<int> a = new List<int>() { B.b[0] }; 
} 
public class B { 
    public static List<int> b = new List<int>() { A.a[0] }; 
} 

и попытаться получить доступ к ним с этим кодом:

try { Console.WriteLine(B.b); } catch (Exception e) { Console.WriteLine(e.InnerException.Message.); } 
try { Console.WriteLine(A.a); } catch (Exception e) { Console.WriteLine(e.InnerException.Message); } 
try { Console.WriteLine(B.b); } catch (Exception e) { Console.WriteLine(e.InnerException.Message); } 

мы получаем следующий вывод:

The type initializer for 'A' threw an exception. 
Object reference not set to an instance of an object. 
The type initializer for 'A' threw an exception. 

Так инициализация B вызывает исключение в статическом конструкторе A и поле слева a со значением по умолчанию (null). Поскольку a is null, b также не может быть правильно инициализирован.

Если у нас нет циклических зависимостей, все работает нормально.


EDIT: Только в случае, если вы не читали комментарии, Jon Skeet обеспечивает очень интересное чтение: The differences between static constructors and type initializers.

0

Да, вам повезло. C#, похоже, выполняет код в том порядке, в котором он появляется в классе.

static private List<int> a = new List<int>() { 0 }; 
static private List<int> b = new List<int>() { a[0] }; 

будет работать, но ...

static private List<int> b = new List<int>() { a[0] }; 
static private List<int> a = new List<int>() { 0 }; 

потерпит неудачу.

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

static MyClass() 
{ 
    a = new List<int>() { 0 }; 
    b = new List<int>() { a[0] }; 
} 
2

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

static private List<int> a; 
static private List<int> b; 

static SomeClass() 
{ 
    a = new List<int>() { 0 }; 
    b = new List<int>() { a[0] }; 
} 

Тогда вам не нужно гадать, что происходит, и вы четко понимаете свои намерения.

14

См section 10.4 of the C# spec для правил здесь:

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

Итак, другими словами, в вашем примере «Ъ» инициализируется в состояние по умолчанию (нуль), и поэтому ссылки на него в инициализаторе «а» является законным, но приведет к NullReferenceException.

Эти правила отличаются от Java (см. section 8.3.2.3 of the JLS для правил Java о прямых ссылках, которые являются более ограничительными).

Смежные вопросы