Проблема с этой строкой:
public static InstanceContext site = new InstanceContext(new CallbackHandler());
Эта строка действительно злая!
Статическая инициализация CallbackHandler
должна быть завершена до того, как new CallbackHandler()
из строки, указанной выше (потому что это создаст экземпляр). Но эта строка является неявно частью статического конструктора! Поэтому я предполагаю, что среда выполнения .NET не может выполнить эту строку и оставляет site
неинициализированным (или инициализированным позже). Вот почему при proxy
инициализации site
все еще null
.
Кстати, я не уверен, определен ли вообще порядок статических инициализаций. Рассмотрим такой пример:
class Test
{
static Twin tweedledum = new Twin(tweedledee);
static Twin tweedledee = new Twin(tweedledum);
}
Edit:
параграф 10.4.5.1 спецификации языка C # говорит, что статические поля инициализируются в текстовом порядке, без учета зависимостей.
Edit:
Эврика! часть 10.11 спецификации языка C # четко гласит:
Можно построить циклические зависимости, позволяющие наблюдать статические поля с инициализаторами переменных в их состоянии по умолчанию.
То, что мы сделали, действительно является круговой зависимостью: CallbackHandler
зависит от самого себя. Таким образом, поведение, которое вы получаете, фактически задокументировано и соответствует стандарту.
Edit:
Как ни странно, когда я тестирую код ( здесь и здесь ), я получаю статический конструктор, работающий после завершения конструктора экземпляра. Как это возможно?
Edit:
Получив ответ на этот вопрос, я могу объяснить, что происходит.
В 1-м случае ваш код неявно переписывается как
public static InstanceContext site;
public static ServiceReference1.StockServiceClient proxy;
static CallbackHandler()
{
site = new InstanceContext(new CallbackHandler());
proxy = new ServiceReference1.StockServiceClient(site);
}
Во втором случае вы получите
public static InstanceContext site;
public ServiceReference1.StockServiceClient proxy;
static CallbackHandler()
{
site = new InstanceContext(new CallbackHandler()); // (*)
}
public CallbackHandler()
{
proxy = new ServiceReference1.StockServiceClient(site);
}
Во втором случае конструктор экземпляра в строке, помеченной (*), запускается до завершения статического конструктора (да, это возможно), но в этот момент site
все еще не назначен.
Итак, в основном во втором варианте кода у вас в каждом экземпляре есть отдельный прокси, который указывает на статический сайт, который, в свою очередь, ссылается на какой-то другой экземпляр по умолчанию CallbackHandler
. Это действительно то, что вы хотите? Может быть, вам просто нужно иметь site
поле экземпляра?
Итак, во 2-м варианте кода происходит следующее:
- Основные пуски.
- Перед строкой
CallbackHandler cbh = new CallbackHandler();
статический конструктор CallbackHandler
называется
- Аргумент для
new InstanceContext
рассчитывается
- Выполнен конструктор
new CallbackHandler()
- Как часть конструктора,
proxy
инициализируется с помощью new ServiceReference1.StockServiceClient(site)
, значение site
равно null
. Это бросает, но давайте забудем об этом только сейчас и рассмотрим, что будет дальше.
- С вычисленным аргументом конструктор
new InstanceContext
называется
- Результат конструктора присваивается
site
, который теперь больше не null
. На этом заканчивается статический конструктор
- Теперь можно запустить конструктор, вызванный в
Main
.
- Как часть этого, создается новый
proxy
, теперь со значением не null
site
- Только что созданный
CallbackHandler
присваивается переменной cbh
.
В вашем случае ServiceReference1.StockServiceClient(site)
выбрасывает, потому что site
равно null
; если его не волнует null
s, код будет работать так, как описано выше.