简体   繁体   English

如果字段是实例成员,则使用Callback Handler异常

[英]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 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. 因此,当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 to site ), it waits for static field site to get initialized ( so that it can assign site's reference to its instance member proxy ). 因此,当CLR尝试实例化实例O (然后它将分配给site )时,它等待静态字段site进行初始化(以便它可以将site's引用分配给其实例成员proxy )。 while site waits for O to get created, which in turn would initialize site field. site等待创建O ,这反过来会初始化site字段。 Since this could create a deadlock of sort, CLR detects this potential deadlock and sets site 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: 因此,在代码的第二个变体中会发生以下情况:

  1. Main starts. 主要开始。
  2. Before the line CallbackHandler cbh = new CallbackHandler(); 行之前CallbackHandler cbh = new CallbackHandler(); the static constructor of CallbackHandler is called 调用CallbackHandler的静态构造函数
  3. The argument for new InstanceContext is calculated 计算new InstanceContext的参数
    • The constructor new CallbackHandler() is executed 执行构造函数new CallbackHandler()
    • As a part of constructor, proxy is initialized with the new ServiceReference1.StockServiceClient(site) , the value of site is null . 作为构造函数的一部分,使用new ServiceReference1.StockServiceClient(site)初始化proxysite值为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 使用计算的参数,将调用构造函数new InstanceContext
  5. The result of the constructor is assigned to site , which is now not null any more. 构造函数的结果被分配给site ,现在不再是null This finishes the static constructor 这样就完成了静态构造函数
  6. Now, the constructor called in Main can start. 现在,在Main调用的构造函数可以启动。
    • As a part of it, a new proxy is constructed, now with non- null value of site 作为其一部分,构建了一个新的proxy ,现在具有非nullsite
  7. The just created CallbackHandler is assigned to the variable cbh . 刚刚创建的CallbackHandler被赋值给变量cbh

In your case, ServiceReference1.StockServiceClient(site) throws because site is null ; 在您的情况下, ServiceReference1.StockServiceClient(site)抛出因为sitenull ; 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM