[英]Exception with Callback Handler if a field is an instance member
希望有人帮助我
如果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
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
为null(无论proxy
是实例还是静态成员)只是因为它是用CallbackHandler
初始化的,如下所述:
因此,当CLR尝试实例化一个实例
O
(然后它将分配给site
)时,它等待静态字段site
进行初始化,而site
等待创建O
,这反过来会初始化site
字段。 由于这可能会造成排序死锁,因此site
“更明智”,因此设置为默认值null?!
然后我记得如果proxy
也是静态的,那么该site
不为null,所以我对正在发生的事情的理解改为:
因此,当CLR尝试实例化实例
O
(然后它将分配给site
)时,它等待静态字段site
进行初始化(以便它可以将site's
引用分配给其实例成员proxy
)。 当site
等待创建O
,这反过来会初始化site
字段。 由于这可能会造成排序死锁,因此CLR会检测到此潜在的死锁并将site
设置为null。
但是后来我运行了你的测试代码,由于某种原因,即使proxy
不是静态的,也会分配site
(因此不为空):
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
}
}
}
到底是怎么回事?
问题出在这一行:
public static InstanceContext site = new InstanceContext(new CallbackHandler());
这条线真的很邪恶!
必须在执行上面给出的行的new CallbackHandler()
之前完成CallbackHandler
的静态初始化(因为这会创建一个实例)。 但是,这条线是隐含的静态构造函数的一部分! 所以我认为.NET运行时无法执行此行,并使site
未初始化(或稍后初始化)。 这就是proxy
初始化site
仍为null
。
顺便说一下,我不确定是否定义了静态初始化的顺序。 考虑这样一个例子:
class Test
{
static Twin tweedledum = new Twin(tweedledee);
static Twin tweedledee = new Twin(tweedledum);
}
编辑:
C#语言规范的第10.4.5.1段说明静态字段是按文本顺序初始化的,而不是考虑依赖性。
可以构造循环依赖关系,允许在其默认值状态下观察具有可变初始值设定项的静态字段。
我们所做的确实是一个循环依赖: CallbackHandler
依赖于它自己。 因此,您获得的行为实际上已记录在案,并且符合标准。
编辑:
奇怪的是,当我测试代码( 这里和这里 )时,我在实例构造函数完成后运行静态构造函数。 这怎么可能?
编辑:
得到了这个问题的答案,我可以解释会发生什么。
在第一种情况下,您的代码被隐式重写为
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()
new ServiceReference1.StockServiceClient(site)
初始化proxy
, site
值为null
。 抛出,但让我们暂时忘记这一点,并考虑接下来会发生什么。 new InstanceContext
site
,现在不再是null
。 这样就完成了静态构造函数 Main
调用的构造函数可以启动。
proxy
,现在具有非null
的site
值 CallbackHandler
被赋值给变量cbh
。 在您的情况下, ServiceReference1.StockServiceClient(site)
抛出因为site
为null
; 如果它不关心null
s,代码将如上所述运行。
通过使字段非静态,您将每个实例与static
实例相关联, 包括static
实例本身 。
换句话说,您正在尝试创建一个在其存在之前使用自身的对象。
通过使该字段为静态,可以断开proxy
与单个实例的连接。
因此, static
实例(必须在proxy
之前创建)不会尝试创建proxy
,并且它可以工作。
当它被声明为静态时,你从未调用构造函数(至少在你拥有的代码中)。 它只会在您第一次访问静态成员时初始化它。 我打赌如果你试图访问它,你会得到同样的例外。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.