Исключение с обработчиком обратного вызова, если поле является членом экземпляра - PullRequest
3 голосов
/ 10 ноября 2010

Надеюсь, что кто-то поможет мне с этим

Если CallbackHandler.proxy статичен, то все работает нормально:

using System;
using System.ServiceModel;

namespace ConsoleApplication5
{
    // Define class which implements callback interface of duplex contract
    public class CallbackHandler : ServiceReference1.IStockServiceCallback
    {
        public static InstanceContext site = new InstanceContext(new CallbackHandler());
        public static ServiceReference1.StockServiceClient proxy = new ServiceReference1.StockServiceClient(site);

        //  called from the service
        public void PriceUpdate(string ticker, double price)
        {
        }  
    }

    class Program
    {
        static void Main(string[] args)
        {
            CallbackHandler cbh = new CallbackHandler();
        }
    }
}

Но если я объявлю его как член экземпляра, то получу System.TypeInitializationException: The type initializer for CallBackHandler’ threw an exception. ---> System.ArgumentNullException. Value cannot be null exception

using System;
using System.ServiceModel;

namespace ConsoleApplication5
{
    // Define class which implements callback interface of duplex contract
    public class CallbackHandler : ServiceReference1.IStockServiceCallback
    {
        public static InstanceContext site = new InstanceContext(new CallbackHandler());
        public ServiceReference1.StockServiceClient proxy = new ServiceReference1.StockServiceClient(site);

        //  called from the service
        public void PriceUpdate(string ticker, double price)
        {
        }  
    }

    class Program
    {
        static void Main(string[] args)
        {
            CallbackHandler cbh = new CallbackHandler();
        }
    }
}

Есть идеи, почему при создании CallbackHandler.proxy члена экземпляра возникает исключение?

РЕДАКТИРОВАТЬ:

ВВо втором случае конструктор экземпляра в строке, помеченной (*), запускается до завершения статического конструктора (да, это возможно), но в этот момент сайт еще не назначен.

Таким образом, во второмcase site должен быть инициализирован как null, тогда как в первом случае ему должно быть присвоено ненулевое значение?!

Но…

Сначала я подумал, что static site имеет значение null (независимо от того, был ли proxy экземпляром или статическим членом) просто потому, что он был инициализирован с CallbackHandler, как объяснено здесь:

Так что, когда CLR пытается создать экземпляр O (что будетзатем присвойте site), он ждет статического поля site, чтобы получить initialized, в то время как site ожидает создания O, что, в свою очередь, инициализирует поле site.Так как это может создать тупик сортировки, site является «мудрее» и, таким образом, получает значение по умолчанию null?!

Тогда я вспомнил, что site не равно нулю, если proxyтакже является статическим, поэтому мое понимание происходящего изменилось на:

Так что, когда CLR пытается создать экземпляр O (который он затем назначил бы site), он ожидает статического поляsite для инициализации (чтобы он мог назначить site’s ссылку на свой элемент экземпляра proxy).в то время как site ожидает создания O, что, в свою очередь, инициализирует поле site.Так как это может создать тупик сортировки, CLR обнаруживает этот потенциальный тупик и устанавливает site в ноль.

Но тогда я запустил ваш тестовый код и по какой-то причине назначен site (таким образом, не является нулевым), даже если proxy не является статичным:

using System;

namespace ConsoleApplication5
{
    public class InstanceContext
    {
        public InstanceContext(CallbackHandler ch)
        {
            Console.WriteLine("new InstanceContext(" + ch + ")");
        }
    }

    public class StockServiceClient
    {
        public StockServiceClient(InstanceContext ic)
        {
            Console.WriteLine("new StockServiceClient(" + ic + ")");
        }
    }

    // Define class which implements callback interface of duplex contract
    public class CallbackHandler
    {
        public static InstanceContext site = new InstanceContext(new CallbackHandler());
        public StockServiceClient proxy = new StockServiceClient(site);
        public CallbackHandler()
        {
            Console.WriteLine("new CallbackHandler()");
        }
        static CallbackHandler()
        {
            Console.WriteLine("static CallbackHandler()");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(CallbackHandler.site == null); // returns false
        }
    }
}

Что происходит?

Ответы [ 3 ]

3 голосов
/ 10 ноября 2010

Проблема с этой строкой:

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-м варианте кода происходит следующее:

  1. Основные пуски.
  2. Перед строкой CallbackHandler cbh = new CallbackHandler(); статический конструктор CallbackHandler называется
  3. Аргумент для new InstanceContext рассчитывается
    • Выполнен конструктор new CallbackHandler()
    • Как часть конструктора, proxy инициализируется с помощью new ServiceReference1.StockServiceClient(site), значение site равно null. Это бросает, но давайте забудем об этом только сейчас и рассмотрим, что будет дальше.
  4. С вычисленным аргументом конструктор new InstanceContext называется
  5. Результат конструктора присваивается site, который теперь больше не null. На этом заканчивается статический конструктор
  6. Теперь можно запустить конструктор, вызванный в Main.
    • Как часть этого, создается новый proxy, теперь со значением не null site
  7. Только что созданный CallbackHandler присваивается переменной cbh.

В вашем случае ServiceReference1.StockServiceClient(site) выбрасывает, потому что site равно null; если его не волнует null s, код будет работать так, как описано выше.

3 голосов
/ 11 ноября 2010

Делая поле нестатичным, вы связываете каждый экземпляр с экземпляром static, , включая сам экземпляр static .

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

Делая поле статическим, вы отключаете proxy от отдельного экземпляра.
Следовательно, экземпляр static (который должен быть создан до proxy) не пытается создать proxy, и это работает.

1 голос
/ 10 ноября 2010

Когда он объявлен как статический, вы никогда не вызывали конструктор (по крайней мере, в коде, который у вас есть). Статический элемент будет инициализирован только при первом обращении к нему. Держу пари, если вы попытаетесь получить к нему доступ, вы получите то же исключение.

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