如果字段是实例成员,则回调处理程序的异常 [英] Exception with Callback Handler if a field is an instance member

查看:28
本文介绍了如果字段是实例成员,则回调处理程序的异常的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

希望有人帮我解决这个问题

如果 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 也有一个实例字段?

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

  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,现在具有 site
    • 的非 null
  7. 刚刚创建的CallbackHandler被赋值给变量cbh.

在您的情况下,ServiceReference1.StockServiceClient(site) 抛出,因为 sitenull;如果它不关心 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 to site ), it waits for static field site to get initialized, while site waits for O to get created, which in turn would initialize site 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 to site ), it waits for static field site to get initialized ( so that it can assign site’s reference to its instance member proxy ). while site waits for O to get created, which in turn would initialize site field. Since this could create a deadlock of sort, CLR detects this potential deadlock and sets site 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:

  1. Main starts.
  2. Before the line CallbackHandler cbh = new CallbackHandler(); the static constructor of CallbackHandler is called
  3. 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.
  4. With the calculated argument, the constructor new InstanceContext is called
  5. The result of the constructor is assigned to site, which is now not null any more. This finishes the static constructor
  6. 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
  7. 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 nulls, the code would run as it is described above.

这篇关于如果字段是实例成员,则回调处理程序的异常的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆