静态构造函数异常

47

我在SO上找了很久,找到了一个最好的答案:这里,不过那个回答针对带有静态构造函数的实例;而我的情况是只使用了静态类。

我的代码:

public static class MailHelper {

    private static string mailHost;

    static MailHelper() {

        var mailSettings = ConfigurationManager.GetSection("MailSettings") as NameValueCollection;
        if (null == mailSettings) {
            throw new ConfigurationErrorsException("Missing Mail Settings in the configuration file");
        }

        mailHost = ConfigurationManager.AppSettings["mailHost"];
        if (null == mailHost) {
            throw new ConfigurationErrorsException("Missing mailHost setting in the configuration file");
        }

    }

    public static void SendMail(MailMessage Message) {
        ...
    }

}


try {
    MailHelper.SendMail(Message);
}
catch (ConfigurationErrorsException exc) {
    ...
}

//  ???    
MailHelper.SendMail(Message);


.
如果静态构造函数在第一次调用时抛出异常,那么我再次尝试访问静态的SendMail()方法会发生什么?
PS:如果您不喜欢Stroustrup版本的K&R括号样式,请不要编辑我的帖子以将大括号更改为您首选的Allman样式。 谢谢。

它应该会失败,但是自己尝试一下有什么不对吗? - Pontus Gagge
我认为从(静态)构造函数中抛出异常并不是很合理,因为这样类就处于不稳定状态(未完全初始化)。那么,创建一个显式的 Init() 函数如何?在使用之前调用它(如果已经初始化,则不执行任何操作),如果它抛出异常,则不要使用该类 - Cameron
Pontus> 我想Jon可能需要更多的分数 :) - James King
6
我并不是Init()方法的铁粉......如果一个类必须使用Init()方法才能正常使用,为什么不直接在构造函数中完成呢?否则你就将控制权交给了调用者,而且你仍然需要在被调用的方法中测试有效性,因为你不能相信调用者会按照预期执行操作。我喜欢下面Chris的回答,它听起来像是你和我之间的一种妥协......在构造函数中设置配置,但不抛出异常,在静态方法中检查设置是否正确。 - James King
谢谢你的私信。终于知道缩进风格有名字了! - krlzlx
3个回答

108

一旦类型初始化程序失败一次,它就不会再重试。该类型在 AppDomain 的生命周期内都是无效的。(请注意,这适用于所有类型初始化程序,而不仅仅是具有静态构造函数的类型。具有初始化器表达式但没有静态构造函数的静态变量类型在类型初始化程序执行的时序方面可能存在细微差异,但它仍然只会发生一次。)

示例:

using System;

public sealed class Bang
{
    static Bang()
    {
        Console.WriteLine("In static constructor");
        throw new Exception("Bang!");
    }

    public static void Foo() {}
}

class Test
{
    static void Main()
    {
        for (int i = 0; i < 5; i++)
        {
            try
            {
                Bang.Foo();
            }
            catch (Exception e)
            {
                Console.WriteLine(e.GetType().Name);
            }
        }
    }
}

输出:

In static constructor
TypeInitializationException
TypeInitializationException
TypeInitializationException
TypeInitializationException
TypeInitializationException

正如你所看到的,静态构造函数只会被调用一次。


9
有趣的是,当你明确抛出Exception时,你只会捕获到TypeInitializationExceptions...那个Exception被吞掉了吗? - James King
11
@James B:不,它在TypeInitializerException的InnerException中。 - Jon Skeet
8
@James:不,你不应该在任何地方捕获TypeInitializationException。那只会发生在类型严重出现问题的情况下。 - Jon Skeet
4
我的理解是,不应该在静态构造函数中抛出异常,也不要到处捕获TypeInitialisationException异常,也不要在测试AppDomains中进行试验。如果你必须使用这些奇怪的方法,那么你几乎肯定已经错过了某些重要的东西。James类的设计显然需要改变。 - Doctor Jones
10
根据http://msdn.microsoft.com/en-us/library/bb386039.aspx...上的说明:从静态构造函数中抛出异常会导致该类型在当前应用程序域中无法使用。你应该有一个非常好的理由(比如安全问题)来从静态构造函数中抛出异常。 - myermian
显示剩余7条评论

26

另外两个答案对你的直接问题给出了很好的回答——这里是一个元答案——当您检测到配置元素未填充时,应该在方法中抛出异常,而不是在构造函数中抛出。在构造阶段,“未配置”是这些元素的有效配置状态,只有在发送邮件时才不是。这将避开整个问题。


1
Jon实际上回答了我问题的最正确的答案,但是这个答案提出了比我拥有的更好的解决方案。 - James King

21

来自Microsoft文档(静态构造函数(C#编程指南)):

如果静态构造函数引发异常,则运行时将不会再次调用它,该类型将在应用程序域的生命周期内保持未初始化状态。通常情况下,当静态构造函数无法实例化类型或出现静态构造函数中未处理的异常时,会抛出TypeInitializationException异常。对于未在源代码中显式定义的隐式静态构造函数,故障排除可能需要检查中间语言(IL)代码。


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