如果字段是实例成员,则使用Callback Handler异常

时间:2010-11-10 19:17:53

标签: c# wcf

希望有人帮助我这个

如果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成为实例成员会抛出异常吗?

修改

  

在第二种情况下,实例   标记为(*)的行中的构造函数   在完成之前运行   静态构造函数(是的,它是   可能),但那时网站是   仍未分配。

因此在第二种情况下site应该初始化为null,而在第一种情况下应该为它分配一个非空值?!

但是...

起初我认为静态site为空(无论proxy是实例还是静态成员),因为它是用CallbackHandler初始化的,如下所述:

  

所以当CLR试图实例化时   实例O(然后它会   分配给site),它等待   得到的静态字段site   初始化,而site等待   O来创建,反过来   会初始化site字段。以来   这可能会造成排序的僵局,   site是“明智的”,因而得到了   设置为默认值null?!

然后我记得如果site也是静态的,proxy不为空,所以我对正在发生的事情的理解改变为:

  

所以当CLR试图实例化时   实例O(然后它会   分配给site),它等待   要初始化的静态字段site   (这样它就可以分配site’s   对其实例成员的引用   proxy)。而site等待O   被创造,反过来会   初始化site字段。从此   可能会造成CLR的死锁   检测到这种潜在的死锁   将site设置为null。

但是我已经运行了您的测试代码,并且由于某种原因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 个答案:

答案 0 :(得分:3)

问题在于这一行:

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);
}

编辑:
paragraph 10.4.5.1 of C# language specs表示静态字段是按文本顺序初始化的,而不是考虑依赖性。

编辑:
找到了! part 10.11 of C# language specs明确指出:

  

可以构造循环依赖关系,允许在默认值状态下观察带有变量初始值设定项的静态字段。

我们所做的确实是循环依赖:CallbackHandler取决于它自己。因此,您获得的行为实际上已记录在案,并且符合标准。

编辑:
很奇怪,当我测试代码(herehere)时,我会在
实例构造函数完成后运行的静态构造函数。这怎么可能?

编辑:
得到this问题的答案,我可以解释会发生什么。

在第一种情况下,您的代码被隐式重写为

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一个实例字段吗?

因此,在代码的第二个变体中会发生以下情况:

  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
  8. 在您的情况下,ServiceReference1.StockServiceClient(site)会因为sitenull而抛出;如果它不关心null s,则代码将如上所述运行。

答案 1 :(得分:3)

通过使字段非静态,您将每个实例与static实例关联,包括static实例本身

换句话说,您正在尝试创建一个在其存在之前使用自身的对象。

通过将字段设为静态,可以从单个实例中断开proxy 因此,static实例(必须在proxy之前创建)不会尝试创建proxy,并且它可以正常工作。

答案 2 :(得分:1)

当它被声明为静态时,你从未调用过构造函数(至少在你的代码中)。它只会在您第一次访问静态成员时初始化它。我打赌如果你试图访问它,你会得到同样的例外。