问题描述
限时送ChatGPT账号..希望有人帮我解决这个问题
如果 CallbackHandler.proxy
是静态的,那么一切正常:
使用系统;使用 System.ServiceModel;命名空间 ConsoleApplication5{//定义实现双工合约回调接口的类公共类 CallbackHandler : ServiceReference1.IStockServiceCallback{public static InstanceContext site = new InstanceContext(new CallbackHandler());public static ServiceReference1.StockServiceClient proxy = new ServiceReference1.StockServiceClient(site);//从服务调用public void PriceUpdate(字符串代码,双倍价格){}}课程计划{static void Main(string[] args){CallbackHandler cbh = new CallbackHandler();}}}
但如果我将其声明为实例成员,则会得到 System.TypeInitializationException: The type initializer for CallBackHandler' 抛出异常.--->System.ArgumentNullException.值不能为空异常
使用系统;使用 System.ServiceModel;命名空间 ConsoleApplication5{//定义实现双工合约回调接口的类公共类 CallbackHandler : ServiceReference1.IStockServiceCallback{public static InstanceContext site = new InstanceContext(new CallbackHandler());public ServiceReference1.StockServiceClient proxy = new ServiceReference1.StockServiceClient(site);//从服务调用public void PriceUpdate(字符串代码,双倍价格){}}课程计划{static void Main(string[] args){CallbackHandler cbh = new CallbackHandler();}}}
知道为什么将 CallbackHandler.proxy
设为实例成员会引发异常吗?
<块引用>
在第二种情况下,实例标记 (*) 行中的构造函数在完成之前运行静态构造函数(是的,它是可能),但此时站点是仍未分配.
因此在第二种情况下 site
应该被初始化为 null,而在第一种情况下它应该被分配一个非空值?!
但是……
起初我认为 static site
是空的(不管 proxy
是实例还是静态成员)仅仅是因为它是用 CallbackHandler
初始化的,如下所述:
所以当 CLR 尝试实例化一个实例 O
(然后它会分配给 site
),它等待静态字段 site
获取已初始化,而 site
等待O
被创建,这反过来将初始化 site
字段.自从这可能会造成某种僵局,site
是更聪明的",因此得到设置为默认值 null?!
然后我想起如果 proxy
也是静态的,site
不是 null,所以我对发生的事情的理解变成了:
所以当 CLR 尝试实例化一个实例 O
(然后它会分配给 site
),它等待静态字段 site
进行初始化(以便它可以分配 site's
对其实例成员的引用代理
).而 site
等待 O
被创建,这反过来又会初始化 site
字段.从此可能会造成某种死锁,CLR检测到这种潜在的死锁并将 site
设置为 null.
但是我已经运行了你的测试代码并且由于某种原因 site
被分配了(因此不是 null )即使 proxy
不是静态的:
使用系统;命名空间 ConsoleApplication5{公共类 InstanceContext{公共实例上下文(CallbackHandler ch){Console.WriteLine("new InstanceContext(" + ch + ")");}}公共类 StockServiceClient{公共 StockServiceClient(InstanceContext ic){Console.WriteLine("new StockServiceClient(" + ic + ")");}}//定义实现双工合约回调接口的类公共类 CallbackHandler{public static InstanceContext site = new InstanceContext(new CallbackHandler());公共 StockServiceClient 代理 = 新 StockServiceClient(site);公共回调处理程序(){Console.WriteLine("new CallbackHandler()");}静态回调处理程序(){Console.WriteLine("static CallbackHandler()");}}课程计划{static void Main(string[] args){Console.WriteLine(CallbackHandler.site == null);//返回假}}}
这是怎么回事?
解决方案问题出在这一行:
public static InstanceContext site = new InstanceContext(new CallbackHandler());
这条线真的很邪恶!
CallbackHandler
的静态初始化必须在之前new CallbackHandler()
从上面给出的行开始被执行(因为这会创建一个实例).但是这一行是隐式的静态构造函数的一部分!所以我想 .NET 运行时无法执行这一行,并且使 site
未初始化(或稍后初始化).这就是为什么在 proxy
初始化时 site
仍然是 null
.
顺便说一下,我不确定是否完全定义了静态初始化的顺序.考虑这样一个例子:
class 测试{静态孪生花呢=新孪生(花呢);静态双花呢=新双胞胎(花呢);}
C# 语言规范的第 10.4.5.1 段 说静态字段按文本顺序初始化,不考虑依赖关系.
尤里卡!C# 语言规范的第 10.11 部分 明确指出:
可以构建循环依赖,允许在默认值状态下观察带有变量初始值设定项的静态字段.
我们所做的确实是一个循环依赖:CallbackHandler
依赖于自身.因此,您获得的行为实际上已记录在案,并且符合标准.
奇怪的是,当我测试代码(here 和 此处),我在实例构造函数完成后运行了静态构造函数.这怎么可能?
得到这个问题的答案后,我可以解释会发生什么了.
在第一种情况下,您的代码被隐式重写为
公共静态InstanceContext站点;公共静态 ServiceReference1.StockServiceClient 代理;静态回调处理程序(){site = new InstanceContext(new CallbackHandler());proxy = new ServiceReference1.StockServiceClient(site);}
在第二种情况下,你得到
公共静态InstanceContext站点;公共 ServiceReference1.StockServiceClient 代理;静态回调处理程序(){site = new InstanceContext(new 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
,现在具有 site
的非 null
值刚刚创建的CallbackHandler
被赋值给变量cbh
.在您的情况下,ServiceReference1.StockServiceClient(site)
抛出,因为 site
是 null
;如果它不关心 null
s,代码会像上面描述的那样运行.
Hope someone helps me with this
If CallbackHandler.proxy
is static, then everything works fine:
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();
}
}
}
But if I declare it as an instance member, then I get 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();
}
}
}
Any idea why making CallbackHandler.proxy
an instance member throws an exception?
EDIT:
In the 2nd case, the instance constructor in the line marked (*) runs before the completion of the static constructor (yes, it's possible), but at that point site is still not assigned.
Thus in second case site
should be initialized to null, while in first case it should be assigned a non-null value?!
But…
At first I thought static site
was null ( regardless of whether proxy
was instance or static member ) simply because it was initialized with CallbackHandler
, as explained here:
So when CLR tries to instantiate an instance
O
( which it would then assign tosite
), it waits for static fieldsite
to get initialized, whilesite
waits forO
to get created, which in turn would initializesite
field. Since this could create a deadlock of sort,site
is "the wiser" and thus gets set to default value null?!
Then I remembered that site
is not null if proxy
is also static, so my understanding of what’s happening changed to:
So when CLR tries to instantiate an instance
O
( which it would then assign tosite
), it waits for static fieldsite
to get initialized ( so that it can assignsite’s
reference to its instance memberproxy
). whilesite
waits forO
to get created, which in turn would initializesite
field. Since this could create a deadlock of sort, CLR detects this potential deadlock and setssite
to null.
But then I’ve run your test code and for some reason site
is assigned ( thus is not null ) even if proxy
is not static:
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
}
}
}
What is going on?
解决方案The problem is with this line:
public static InstanceContext site = new InstanceContext(new CallbackHandler());
This line is really evil!
The static initialization of CallbackHandler
must be finished before the new CallbackHandler()
from the line given above is executed (because this would create an instance). But this line is implicitly a part of the static constructor! So I suppose the .NET runtime cannot execute this line, and leaves site
uninitialized (or initialized later). That's why at the proxy
initialization site
is still null
.
By the way, I am not sure if the order of static initializations is defined at all. Consider such an example:
class Test
{
static Twin tweedledum = new Twin(tweedledee);
static Twin tweedledee = new Twin(tweedledum);
}
Edit:
the paragraph 10.4.5.1 of C# language specs says that the static fields are initialized in the textual order, not considering the dependencies.
Edit:
Eureka! The part 10.11 of C# language specs clearly states:
It is possible to construct circular dependencies that allow static fields with variable initializers to be observed in their default value state.
What we did is indeed a circular dependency: CallbackHandler
depends on itself. So the behaviour you get is actually documented and is according to the standard.
Edit:
Strange enough, when I test the code (here and here), I get static constructor running after instance constructor finishes. How is this possible?
Edit:
having got the answer to this question, I can explain what happens.
In the 1st case, your code is implicitly rewritten as
public static InstanceContext site;
public static ServiceReference1.StockServiceClient proxy;
static CallbackHandler()
{
site = new InstanceContext(new CallbackHandler());
proxy = new ServiceReference1.StockServiceClient(site);
}
In the 2nd case, you get
public static InstanceContext site;
public ServiceReference1.StockServiceClient proxy;
static CallbackHandler()
{
site = new InstanceContext(new CallbackHandler()); // (*)
}
public CallbackHandler()
{
proxy = new ServiceReference1.StockServiceClient(site);
}
In the 2nd case, the instance constructor in the line marked (*) runs before the completion of the static constructor (yes, it's possible), but at that point site
is still not assigned.
So, basically in your second variant of code you have at each instance a separate proxy, which points to a static site, which in turn references some another "default" instance of CallbackHandler
. Is that really what you want? Maybe, you just need to have site
an instance field as well?
So, in the 2nd variant of the code the following happens:
Main starts. Before the lineCallbackHandler cbh = new CallbackHandler();
the static constructor of CallbackHandler
is called
The argument for new InstanceContext
is calculated
The constructor new CallbackHandler()
is executed
As a part of constructor, proxy
is initialized with the new ServiceReference1.StockServiceClient(site)
, the value of site
is null
. This throws, but let's forget about this just now, and consider what would happen next.
With the calculated argument, the constructor new InstanceContext
is called
The result of the constructor is assigned to site
, which is now not null
any more. This finishes the static constructor
Now, the constructor called in Main
can start.
As a part of it, a new proxy
is constructed, now with non-null
value of site
The just created CallbackHandler
is assigned to the variable cbh
.
In your case, ServiceReference1.StockServiceClient(site)
throws because site
is null
; if it wouldn't care about null
s, the code would run as it is described above.
这篇关于如果字段是实例成员,则回调处理程序的异常的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!
更多推荐
[db:关键词]
发布评论