希望有人帮助我这个
如果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
}
}
}
发生了什么事?
答案 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
取决于它自己。因此,您获得的行为实际上已记录在案,并且符合标准。
编辑:
很奇怪,当我测试代码(here和here)时,我会在实例构造函数完成后运行的静态构造函数。这怎么可能?
编辑:
得到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
一个实例字段吗?
因此,在代码的第二个变体中会发生以下情况:
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,则代码将如上所述运行。
答案 1 :(得分:3)
通过使字段非静态,您将每个实例与static
实例关联,包括static
实例本身。
换句话说,您正在尝试创建一个在其存在之前使用自身的对象。
通过将字段设为静态,可以从单个实例中断开proxy
因此,static
实例(必须在proxy
之前创建)不会尝试创建proxy
,并且它可以正常工作。
答案 2 :(得分:1)
当它被声明为静态时,你从未调用过构造函数(至少在你的代码中)。它只会在您第一次访问静态成员时初始化它。我打赌如果你试图访问它,你会得到同样的例外。