繁体   English   中英

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

[英]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段说明静态字段是按文本顺序初始化的,而不是考虑依赖性。

编辑:
找到了! C#语言规范第10.11部分明确指出:

可以构造循环依赖关系,允许在其默认值状态下观察具有可变初始值设定项的静态字段。

我们所做的确实是一个循环依赖: 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的实例字段呢?

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

  1. 主要开始。
  2. 行之前CallbackHandler cbh = new CallbackHandler(); 调用CallbackHandler的静态构造函数
  3. 计算new InstanceContext的参数
    • 执行构造函数new CallbackHandler()
    • 作为构造函数的一部分,使用new ServiceReference1.StockServiceClient(site)初始化proxysite值为null 抛出,但让我们暂时忘记这一点,并考虑接下来会发生什么。
  4. 使用计算的参数,将调用构造函数new InstanceContext
  5. 构造函数的结果被分配给site ,现在不再是null 这样就完成了静态构造函数
  6. 现在,在Main调用的构造函数可以启动。
    • 作为其一部分,构建了一个新的proxy ,现在具有非nullsite
  7. 刚刚创建的CallbackHandler被赋值给变量cbh

在您的情况下, ServiceReference1.StockServiceClient(site)抛出因为sitenull ; 如果它不关心null s,代码将如上所述运行。

通过使字段非静态,您将每个实例与static实例相关联, 包括static实例本身

换句话说,您正在尝试创建一个在其存在之前使用自身的对象。

通过使该字段为静态,可以断开proxy与单个实例的连接。
因此, static实例(必须在proxy之前创建)不会尝试创建proxy ,并且它可以工作。

当它被声明为静态时,你从未调用构造函数(至少在你拥有的代码中)。 它只会在您第一次访问静态成员时初始化它。 我打赌如果你试图访问它,你会得到同样的例外。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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