[英]Exception with Callback Handler if a field is an instance member
Hope someone helps me with this 希望有人帮助我
If CallbackHandler.proxy
is static, then everything works fine: 如果
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();
}
}
}
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
但是,如果我将它声明为实例成员,那么我得到
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();
}
}
}
Any idea why making CallbackHandler.proxy
an instance member throws an exception? 知道为什么让
CallbackHandler.proxy
成为实例成员会引发异常吗?
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?! 因此,在第二种情况下,
site
应该初始化为null,而在第一种情况下应该为它分配一个非空值?!
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: 起初我认为静态
site
为null(无论proxy
是实例还是静态成员)只是因为它是用CallbackHandler
初始化的,如下所述:
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.因此,当CLR尝试实例化一个实例
O
(然后它将分配给site
)时,它等待静态字段site
进行初始化,而site
等待创建O
,这反过来会初始化site
字段。 Since this could create a deadlock of sort,site
is “the wiser” and thus gets set to default value null?!由于这可能会造成排序死锁,因此
site
“更明智”,因此设置为默认值null?!
Then I remembered that site
is not null if proxy
is also static, so my understanding of what's happening changed to: 然后我记得如果
proxy
也是静态的,那么该site
不为null,所以我对正在发生的事情的理解改为:
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
).因此,当CLR尝试实例化实例
O
(然后它将分配给site
)时,它等待静态字段site
进行初始化(以便它可以将site's
引用分配给其实例成员proxy
)。 whilesite
waits forO
to get created, which in turn would initializesite
field.当
site
等待创建O
,这反过来会初始化site
字段。 Since this could create a deadlock of sort, CLR detects this potential deadlock and setssite
to null.由于这可能会造成排序死锁,因此CLR会检测到此潜在的死锁并将
site
设置为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: 但是后来我运行了你的测试代码,由于某种原因,即使
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
}
}
}
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). 必须在执行上面给出的行的
new CallbackHandler()
之前完成CallbackHandler
的静态初始化(因为这会创建一个实例)。 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). 所以我认为.NET运行时无法执行此行,并使
site
未初始化(或稍后初始化)。 That's why at the proxy
initialization site
is still null
. 这就是
proxy
初始化site
仍为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. C#语言规范的第10.4.5.1段说明静态字段是按文本顺序初始化的,而不是考虑依赖性。
Edit: 编辑:
Eureka! 找到了! The part 10.11 of C# language specs clearly states:
C#语言规范的第10.11部分明确指出:
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. 我们所做的确实是一个循环依赖:
CallbackHandler
依赖于它自己。 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. 在第二种情况下,标记为(*)的行中的实例构造函数在静态构造函数完成之前运行(是的,它是可能的),但此时仍未分配
site
。
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
. 因此,基本上在您的第二个代码变体中,您在每个实例中都有一个单独的代理,它指向静态站点,而静态站点又引用了另一个
CallbackHandler
“默认”实例。 Is that really what you want? 这真的是你想要的吗? Maybe, you just need to have
site
an instance field as well? 也许,你只需要拥有
site
的实例字段呢?
So, in the 2nd variant of the code the following happens: 因此,在代码的第二个变体中会发生以下情况:
CallbackHandler cbh = new CallbackHandler();
CallbackHandler cbh = new CallbackHandler();
the static constructor of CallbackHandler
is called CallbackHandler
的静态构造函数 new InstanceContext
is calculated new InstanceContext
的参数
new CallbackHandler()
is executed new CallbackHandler()
proxy
is initialized with the new ServiceReference1.StockServiceClient(site)
, the value of site
is null
. new ServiceReference1.StockServiceClient(site)
初始化proxy
, site
值为null
。 This throws, but let's forget about this just now, and consider what would happen next. new InstanceContext
is called new InstanceContext
site
, which is now not null
any more. site
,现在不再是null
。 This finishes the static constructor Main
can start. Main
调用的构造函数可以启动。
proxy
is constructed, now with non- null
value of site
proxy
,现在具有非null
的site
值 CallbackHandler
is assigned to the variable cbh
. CallbackHandler
被赋值给变量cbh
。 In your case, ServiceReference1.StockServiceClient(site)
throws because site
is null
; 在您的情况下,
ServiceReference1.StockServiceClient(site)
抛出因为site
为null
; if it wouldn't care about null
s, the code would run as it is described above. 如果它不关心
null
s,代码将如上所述运行。
By making the field non-static, you are associating each instance with the static
instance, including the static
instance itself . 通过使字段非静态,您将每个实例与
static
实例相关联, 包括static
实例本身 。
In other words, you are trying to make an object that uses itself before it exists. 换句话说,您正在尝试创建一个在其存在之前使用自身的对象。
By making the field static, you disconnect proxy
from the individual instance. 通过使该字段为静态,可以断开
proxy
与单个实例的连接。
Therefore, the static
instance (which must be created before proxy
) doesn't try to create proxy
, and it works. 因此,
static
实例(必须在proxy
之前创建)不会尝试创建proxy
,并且它可以工作。
When it is declared as static like that, you never called the constructor (at least in the code you have). 当它被声明为静态时,你从未调用构造函数(至少在你拥有的代码中)。 It will only initialize the static member when you access it for the first time.
它只会在您第一次访问静态成员时初始化它。 I'm betting if you try to access it, you will get the same exception.
我打赌如果你试图访问它,你会得到同样的例外。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.