Exception in static constructor
Solution 1
The other two answers are good answers to your direct question - here's a metaanswer - you should be throwing the exception in the method when you detect that the configuration elements are not populated, rather than in the constructor. IMHO, "not configured" is a valid configuration state for those elements at the constructor phase, just not at SendMail time. That will sidestep this whole problem.
Solution 2
Once a type initializer has failed once, it is never retried. The type is dead for the lifetime of the AppDomain. (Note that this is true for all type initializers, not just for types with static constructors. A type with static variables with initializer expressions, but no static constructors, can exhibit subtle differences in the timing of the type initializer execution - but it'll still only happen once.)
Demonstration:
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);
}
}
}
}
Output:
In static constructor
TypeInitializationException
TypeInitializationException
TypeInitializationException
TypeInitializationException
TypeInitializationException
As you can see, the static constructor is only called once.
Solution 3
From the Microsoft Documentation (Static Constructors (C# Programming Guide)):
If a static constructor throws an exception, the runtime will not invoke it a second time, and the type will remain uninitialized for the lifetime of the application domain in which your program is running. Most commonly, a TypeInitializationException exception is thrown when a static constructor is unable to instantiate a type or for an unhandled exception occurring within a static constructor. For implicit static constructors that are not explicitly defined in source code, troubleshooting may require inspection of the intermediate language (IL) code.
Related videos on Youtube
James King
Updated on March 02, 2021Comments
-
James King about 3 years
I've dug around SO for an answer to this, and the best one I can find so far is here, however that is geared toward instances with static constructors; I'm only using the class statically.
My code:
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); .
So if the static constructor throws an exception the first time it's called, what happens the second time I try to access the static SendMail() method?
PS: Sorry if you don't like Stroustrup's version of K&R brace styling, but don't edit my post just to change the braces to your preferred Allman style. Thanks.
-
Pontus Gagge over 13 yearsIt should fail, but what's wrong with trying it out for yourself?
-
Cameron over 13 yearsI don't think it really makes sense to throw from a (static) constructor, because then the class is in an unstable state (not fully initialized). How about creating an explicit
Init()
function which you call before use (it should do nothing if already initialized), and if it throws an exception, don't use the class -
James King over 13 yearsPontus> I thought Jon might need some more points : )
-
James King over 13 yearsCameron> I've never been a big fan of Init() methods... if Init() is required to make a class usable, why not just do it in the constructor? Otherwise you're passing control of your object to the caller, and you still have to test for validity in the called methods, because you can't trust that the caller did what he was supposed to. I like Chris' answer below, it sounds like a compromise between your approach and mine... set the config in the constructor, but don't throw exceptions, and check the settings in the static method instead.
-
krlzlx over 4 yearsThanks for the PS. Finally learned that there's names for indentation styles!
-
-
James King over 13 yearsIt's interesting that you catch only
TypeInitializationExceptions
when you explicitly throwException
... does thatException
get swallowed? -
Jon Skeet over 13 years@James B: No, it's in the InnerException of the TypeInitializerException.
-
James King over 13 yearsJon actually answered my question most correctly, but this answer proposes a better solution than the one I have.
-
James King over 13 yearsAh! Hope you don't mind, I awarded the answer to Chris... Your question actually answers the question I was asking and was the most helpful in understanding the expected behavior, but I liked his solution to avoid the problems with my approach. Catching TypeInitializationExceptions everywhere I make static calls to my class doesn't sound like fun! Thanks for your help!
-
Jon Skeet over 13 years@James: No, you shouldn't be catching TypeInitializationException anywhere. That should only occur if a type is fatally broken, basically.
-
James King over 13 yearsHmm, back to confused, then... If calling into my static class is going to potentially throw TypeInitializationExceptions, why wouldn't I want to trap for them?
-
JerKimball about 11 yearsJon - it would seem, per this behavior (especially the whole "dead for the lifetime of the AppDomain" statement) that it would make sense that any type that might fail on static initialization should be first "Test loaded" into a throwaway AppDomain; does this sound correct?
-
Jon Skeet about 11 years@JerKimball: Well, I think it's at least unusual that you'd be able to keep going sensible with one of your types being dead. It's even more unusual that the error would be transient, although I guess it's feasible.
-
JerKimball about 11 yearsGranted, it'd be an unusual situation to say the least; I imagine it'd be more common in "plug-in" type scenarios...I suppose it makes sense from the loader perspective; as you say, it's unlikely that a type load error would be transient, therefore the smartest course of action is to take the "I'm afraid I can't let you do that, Dave" approach...good to know behavior, tho!
-
Doctor Jones about 11 yearsI think the takeaway from this is to not throw exceptions from within a Static Constructor, not to catch
TypeInitialisationException
everywhere, not to mess around with test AppDomains. If you have to start jumping through these weird hoops you've almost certainly missed something. The design of James' class clearly needs to change. -
VoodooChild about 11 years@JonSkeet - "The type is dead for the lifetime of the AppDomain" - what does it mean by lifetime of the appdomain? Lets say this type is in a WCF Service & IIS hosted, where multiple clients can be connected, but the very first client produces an error when calling the service in service's static constructor (specific to the first client only) making the type invalid, then after that no other client can make that WCF call. So we restart the IIS site or recycle app pool in IIS to force that a new appdomain is started? Or what kind of things determine that?
-
myermian about 11 yearsAccording to msdn.microsoft.com/en-us/library/bb386039.aspx ... it states:
Throwing exceptions from a static constructor causes the type to be unusable in the current application domain. You should have a very good reason (such as a security issue) for throwing an exception from a static constructor.
-
pholpar almost 8 years@VoodooChild: Yes, in the case of a web-based application (like a WCF service) the app domain means the process of the IIS application pool. We had a similar issue with a custom SharePoint application, see pholpar.wordpress.com/2016/06/30/…. A web application is a very special case of such potential issues, as it provides access for multiple user identities. If your application supports impersonation, the identity of the first user accessing the static constructor may affect all other users.