using System; class Test { static void Main() { Console.WriteLine(First.Beta); } } class First { public static readonly int Alpha = 5; public static readonly int Beta = Second.Gamma; } class Second { public static readonly int Gamma = First.Alpha; }
It’s not a problem. If a static constructor (§10.12) exists, the execution of the static field initializers occurs immediately prior to executing that static constructor. Otherwise, the field of initialization of the static field of the field of implementation is dependent on the time of use.
The order of initialization of static fields of the class corresponds to the order of their location in the source text of the class. If there is a static constructor in the class, the initialization code for the static fields of the class is located right before the static constructor call. Otherwise, if the static constructor does not exist, the initialization of the static fields is performed in a location dependent on the specific implementation: this happens before the first use of the static field.
CLI
specification, which reveals more details on type initialization, especially cyclic dependencies and multi-threading, can be cited. However, I will not do this, but only write a couple of short excerpts:CLI
notices that type A needs to be initialized while it is also in the process of initialization in the same thread, CLI
continues to work as if type A has already been initialized.Test
: no further action requiredMain
First
(since we need First.Beta
)First.Alpha
to 5Second
(since we need Second.Gamma
)Second.Gamma
to First.Alpha
(5)Second
InitializationFirst.Beta
to Second.Gamma
(5)First
Test
: no further action requiredMain
First
(since we need First.Beta
)Second
(we will need Second.Gamma
)Second.Gamma
to First.Alpha
(0)Second
InitializationFirst.Alpha
to 5First.Beta
to Second.Gamma
(0)First
First
order to get further First.Alpha
. However, this thread is already initializing First
, so we skip initialization, hoping that everything is in order. However, at this point, the initialization of the variable has not yet occurred. Oops ...Second
(for example, to access another variable), then we will get a completely different result. And, in practice, you can get a situation where the launch of all unit tests will lead to the fact that all of them will be overwhelmed. But if at the same time run them separately, they will work (it is quite possible, except for one).Encoding.Utf8
, or TimeZoneInfo.Utc
. Notice that in both cases these are static properties, but it seems to me that they carry with them the use of static fields. At first glance, it seems that using public static readonly
public static get-only
properties is the same, however, as we will see later, using properties gives its advantages.UTC
time zone, or the ISO calendar system
. Moreover, in addition to publicly visible values, we have a lot of static variables used inside the library (mainly for caching tasks). All this makes the library more difficult and difficult to test, but the performance benefits in this case are very, very significant.AppDomain
will be initialized only once. As I showed earlier, it is possible that with one initialization order this will cause an error, and with any other, no error will occur.NCrunch
. And I'm sure that if I fix this, the rest of the unit-testing systems will still break my program. private static readonly int TypeInitializationChecking = NodaTime.Utility.TypeInitializationChecker.RecordInitializationStart();
static readonly field
creates for you a cyclic dependency, you use the static readonly property
, which returns an internal static readonly field
, in a nested, private static class. We still have thread-safe initialization with a single call guarantee, but the nested
type will not be initialized until there is a need for it. // Requires Bar to be initialized - if Bar also requires Foo to be // initialized, we have a problem... public static readonly Foo SimpleFoo = new Foo(Bar.Zero);
public static readonly Foo SimpleFoo { get { return Constants.SimpleFoo; } } private static class Constants { private static readonly int TypeInitializationChecking = NodaTime.Utility.TypeInitializationChecker.RecordInitializationStart(); // This requires both Foo and Bar to be initialized, but that's okay // so long as neither of them require Foo.Constants to be initialized. // (The unit test would spot that.) internal static readonly Foo SimpleFoo = new Foo(Bar.Zero); }
NDepend
help me with this.Noda Time
I found all the cyclical dependencies. It is worth trying it out on your own code - see where you may have hidden problems. internal sealed class TypeInitializationChecker : MarshalByRefObject { private static List<Dependency> dependencies = null; private static readonly MethodInfo EntryMethod = typeof(TypeInitializationChecker).GetMethod("FindDependencies"); internal static int RecordInitializationStart() { if (dependencies == null) { return 0; } Type previousType = null; foreach (var frame in new StackTrace().GetFrames()) { var method = frame.GetMethod(); if (method == EntryMethod) { break; } var declaringType = method.DeclaringType; if (method == declaringType.TypeInitializer) { if (previousType != null) { dependencies.Add(new Dependency(declaringType, previousType)); } previousType = declaringType; } } return 0; } /// <summary> /// Invoked from the unit tests, this finds the dependency chain for a single type /// by invoking its type initializer. /// </summary> public Dependency[] FindDependencies(string name) { dependencies = new List<Dependency>(); Type type = typeof(TypeInitializationChecker).Assembly.GetType(name, true); RuntimeHelpers.RunClassConstructor(type.TypeHandle); return dependencies.ToArray(); } /// <summary> /// A simple from/to tuple, which can be marshaled across AppDomains. /// </summary> internal sealed class Dependency : MarshalByRefObject { public string From { get; private set; } public string To { get; private set; } internal Dependency(Type from, Type to) { From = from.FullName; To = to.FullName; } } }
[TestFixture] public class TypeInitializationTest { [Test] public void BuildInitializerLoops() { Assembly assembly = typeof(TypeInitializationChecker).Assembly; var dependencies = new List<TypeInitializationChecker.Dependency>(); // Test each type in a new AppDomain - we want to see what happens where each type is initialized first. // Note: Namespace prefix check is present to get this to survive in test runners which // inject extra types. (Seen with JetBrains.Profiler.Core.Instrumentation.DataOnStack.) foreach (var type in assembly.GetTypes().Where(t => t.FullName.StartsWith("NodaTime"))) { // Note: this won't be enough to load the assembly in all test runners. In particular, it fails in // NCrunch at the moment. AppDomainSetup setup = new AppDomainSetup { ApplicationBase = AppDomain.CurrentDomain.BaseDirectory }; AppDomain domain = AppDomain.CreateDomain("InitializationTest" + type.Name, AppDomain.CurrentDomain.Evidence, setup); var helper = (TypeInitializationChecker)domain.CreateInstanceAndUnwrap(assembly.FullName, typeof(TypeInitializationChecker).FullName); dependencies.AddRange(helper.FindDependencies(type.FullName)); } var lookup = dependencies.ToLookup(d => d.From, d => d.To); // This is less efficient than it might be, but I'm aiming for simplicity: starting at each type // which has a dependency, can we make a cycle? // See Tarjan's Algorithm in Wikipedia for ways this could be made more efficient. // http://en.wikipedia.org/wiki/Tarjan's_strongly_connected_components_algorithm foreach (var group in lookup) { Stack<string> path = new Stack<string>(); CheckForCycles(group.Key, path, lookup); } } private static void CheckForCycles(string next, Stack<string> path, ILookup<string, string> dependencyLookup) { if (path.Contains(next)) { Assert.Fail("Type initializer cycle: {0}-{1}", string.Join("-", path.Reverse().ToArray()), next); } path.Push(next); foreach (var candidate in dependencyLookup[next].Distinct()) { CheckForCycles(candidate, path, dependencyLookup); } path.Pop(); } }
Source: https://habr.com/ru/post/143936/
All Articles