为什么这个静态构造函数没有被调用?

19

我正在创建一个ASP.NET Web服务。我有一个类,其静态构造函数在我尝试初始化该类的对象时没有被调用。我无法理解这种行为。在静态构造函数内,我正在从web.config文件中读取值。

以下是代码的一部分:

[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class Service : System.Web.Services.WebService
{
    AppController extractor;

    public Service()
    {
        try
        {
            extractor = new AppController();
        }
        catch(Exception ex)
        {
            // I am not getting exception at this point.
        }
    }
}

public class AppController
{
    static string converterBatchFilePath = null;
    static string personalProfileOutputFolderPath = null;

    static AppController()
    {
        // reading some settings from web.config file
        try
        {
            converterBatchFilePath = ConfigurationManager.AppSettings["WordToTextConverterBatFilePath"];
        }
        catch(Exception ex)
        { // }
    }
    public AppController()
    {
        // do some initialization
    }
}

在调试 Web 服务时,我发现只有实例构造函数被调用,而控制流从未进入静态构造函数。

有人知道这是为什么吗?

我正在使用 VS 2008 Express 版本和 C#。

编辑

实际上,这个 AppController 是一个基于控制台的项目。我已将该项目作为引用添加到 Web 服务项目中,然后在其中使用它。如果我从命令行中使用 AppController,则可以正常工作,但从 Web 服务项目中无法正常工作。


3
我怀疑你的诊断结果有误 - 你能否编写一个简短但完整的程序来展示这种情况? - Jon Skeet
2
我会尝试的一件事是(暂时)将 static AppController 的内容放入 try...catch 中,并显示/记录异常。 - CodesInChaos
为什么你说“控制永远不会到达静态构造函数”?是因为断点没有被触发还是因为那里应该设置的值是错误的(空的)? - Hans Kesting
@Hans Kesting,两个原因。AppController类的静态构造函数从web.config文件中读取值。这些静态成员变量没有获得值。因此,我在静态构造函数的第一句话和实例构造函数内部放置了调试点。程序控制直接跳转到实例构造函数内的调试点。 - Shekhar
它在静态类的第一次调用时被调用。例如,当您从静态类中调用方法(第一次)时,它首先调用静态构造函数。 - Mahdi Ataollahi
显示剩余8条评论
6个回答

22

今天我的静态初始化器没有被调用。原来访问类的常量成员之前是不会调用静态初始化器的。

由于const值在编译时已知,这很合理,但这意味着文档中所述的“在…任何静态成员被引用之前自动调用”在技术上是不正确的,至少与@JonSkeet的断言 “所有常量声明都是隐式静态的”相结合时如此。

以下程序演示了此问题:

using System;

static class Program
{
    public static void Main()
    {
        Console.WriteLine("Constant={0}", Problem.Constant);
        Console.WriteLine("ReadOnly={0}", Problem.ReadOnly);
        Console.WriteLine("Field={0}", Problem.Field);
        Console.WriteLine("Property={0}", Problem.Property);
    }

    private static class Problem
    {
        public const int Constant = 1;
        public static readonly int ReadOnly = 2;
        public static int Field = 3;
        private static int mProperty = 4;
        public static int Property { get { return mProperty; } }

        static Problem()
        {
            Console.WriteLine("Problem: static initializer");
        }
    }
}

输出结果为:

Constant=1
问题:静态初始化程序
ReadOnly=2
Field=3
Property=4

(已使用.NET 4.5进行测试。)


5
我观察到了与此处描述的相同的行为。这让人非常困惑,与文档描述相矛盾。 - Boris Zinchenko
Const是一个关键字,用于声明编译时常量。它在内存中从不作为变量存在。此外,为什么需要在反射中调用特殊方法来获取值,因为它实际上必须实例化一个新实例来保存该值。您还可以通过查看为函数创建的IL代码来了解此情况。.Constant被一个逐字的1所取代。 - Cine

22

我的猜测是它在你预期之前被调用了。如果您已经调试了站点但未回收AppPool,则很可能已经运行了静态构造函数。 同样,如果尚未调用静态构造函数,则访问任何静态成员的任何内容也将调用静态构造函数。


2
你是对的。应用程序开发服务器已经在运行了。我停止了它并重新启动了Web服务。现在一切都正常了。非常感谢!!! - Shekhar
1
@Shekhar 在与 AppDomain/进程相关的静态变量行为方面,IIS 的影响更加明显,因为 IIS 可以随时回收进程,这意味着静态变量也会被回收。 - Adam Houldsworth
@Adam,进程的回收会如何影响静态变量?你知道哪些阅读材料/书籍/博客可以让我获取更多相关信息吗? - Shekhar
1
@Shekhar,这似乎只与AppDomains有关:http://bytes.com/topic/c-sharp/answers/757645-static-variable-lifetime-webservice - Adam Houldsworth
四年后,仍然通过这个答案帮助人们。 - T.J. Crowder

3
静态构造函数用于初始化任何静态数据或执行仅需执行一次的特定操作。它会在第一个实例被创建之前或任何静态成员被引用之前自动调用。
请注意,静态构造函数会在程序中创建第一个实例或引用任何静态成员之前自动调用,用户无法控制静态构造函数何时执行。 来自MSDN's Static Constructors (C# Programming Guide)

14
这并没有解释为什么实例构造函数可以在静态构造函数未被调用的情况下被调用... - Jon Skeet
这只是表明它应该被调用(因为实例已经创建,静态构造函数保证已运行),但并没有解释OP观察到的行为。 - CodesInChaos
1
@Shekhar,有很多因素可能会导致断点无法被触发,例如某些调试属性或VS设置。我创建了一个空项目,在构造函数中没有任何内容,我的静态构造函数可以正常调用,使用的是C# 4、.Net 4和VS 2010。 - Adam Houldsworth
@Shekhar:好的,我同意这个事实,但可能是因为您在一个服务上初始化了类而在另一个服务上没有。 - Sandeep Pathak
确实,这对我来说是答案 - 我想知道为什么我的 Web 服务中的静态构造函数没有被调用。答案是,在 VS 有机会附加之前,它已经以某种方式被调用了。添加 Debugger.Break(),然后我的构造函数中的断点开始工作了。 - neminem
显示剩余2条评论

3
我猜测你的问题是由静态构造函数抛出并被创建实例的代码忽略所引起的异常。可能甚至是一个静态字段初始化器中的异常,这更难以调试。也许启用“第一次机会异常”可以帮助调试该问题。
我不会将从配置文件读取的代码放入静态构造函数中。相反,我会封装所有与配置有关的内容,并将该类的实例传递到您的构造函数中,可能使用IoC容器。这具有许多优点:
  • 在同一AppDomain中同时使用不同的配置
  • 可以使用其他方式加载配置
  • 您不需要在静态构造函数中执行可能失败的复杂操作。正如你所看到的,静态构造函数往往很难调试,所以我只会进行不依赖于外部状态的简单初始化
(我知道这不是答案,但它太长了不能作为评论返回)

1

这是我快速编写的一个示例,用于从配置文件中获取提取器类中的值,包括静态和实例构造函数。这对我很有效--将其与您正在执行的操作进行比较并查看有何不同:

public class Service : System.Web.Services.WebService
{
    AppController extractor;

    [WebMethod]
    public string HelloWorld()
    {
        extractor = new AppController();
        return AppController.staticString + " :: " + extractor.instanceString;
    }
}

class AppController
{
    public static string staticString;
    public string instanceString;

    static AppController()
    {
        staticString = System.Configuration.ConfigurationManager.AppSettings["static"];
    }
    public AppController()
    {
        instanceString = System.Configuration.ConfigurationManager.AppSettings["instance"];
    }
}

My web.config:

  <appSettings>
    <add key="static" value="blah blah"/>
    <add key="instance" value="ha ha"/>
  </appSettings>

我的回复:

<?xml version="1.0" encoding="UTF-8"?>
<string xmlns="http://tempuri.org/">blah blah :: ha ha</string>

1
当我试图在静态字段引用的行上放置断点进行调试时,我没有在静态构造函数上获得调试控制。
我将断点保留在静态构造函数入口处,并从引用静态字段的行中删除了断点。现在调试控制开始进入静态构造函数代码。 此图显示了带有断点的编辑器外观

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接