tabs ↹ over ␣ ␣ ␣ spaces

by Jiří {x2} Činčura

Going deeper into static constructors hole

8 Sep 2021 2 mins .NET, .NET Core

Yesterday’s exploration led to another eye-opening moment. This goes even deeper. As you maybe know, dependencies are shared between AssemblyLoadContexts. This can lead to subtle changes of behavior depending on what’s loaded where.

Let’s create a separate assembly ClassLibrary with a simple code.

namespace ClassLibrary1
{
	public static class FooBarBaz
	{
		public static void Test()
		{
			FooBar.Test();
		}
	}
}

And expand a little bit code from yesterday.

namespace StaticCtorALC
{
	class Program
	{
		static void Main()
		{
			FooBar.Test();
			var alc = new AssemblyLoadContext("Test");
			//alc.LoadFromAssemblyPath("StaticCtorALC.dll");
			var assembly = alc.LoadFromAssemblyPath("ClassLibrary1.dll");
			var type = assembly.GetType("ClassLibrary1.FooBarBaz");
			var method = type.GetMethod("Test");
			method.Invoke(null, null);

			Console.WriteLine();
			foreach (var c in AssemblyLoadContext.All)
			{
				Console.WriteLine(c.Name);
				foreach (var a in c.Assemblies)
				{
					Console.WriteLine($"  {a.FullName}");
				}
			}
		}
	}

	public static class FooBar
	{
		static FooBar()
		{
			Console.WriteLine("Hello! ;)");
		}

		public static void Test()
		{
			Console.WriteLine("Test");
		}
	}
}

Because the dependencies are shared, the Hello! ;) is printed only once. But if I load the StaticCtorALC.dll manually explicitly into my Test AssemblyLoadContext (the commented-out line) I get Hello! ;) again twice.

In above case I controlled all the loading. In real world you might not have complete control and complete picture. You can go further and start thinking about what happens when you load the StaticCtorALC.dll later and even putting next AssemblyLoadContext into the mix – suddenly the cctors calls depend on order of loads into other AssemblyLoadContexts. Still deterministic, but one extra call can change the order (and behavior) dramatically. Queue head explosion.

All this reminds me precautions when dealing with multi-threaded behavior or locking. Extremely easy to crumple everything down with almost invisible change too. Let’s hope I’ll make it through all the ins and outs of AssemblyLoadContext in the future.

Profile Picture Jiří Činčura is .NET, C# and Firebird expert. He focuses on data and business layers, language constructs, parallelism, databases and performance. For almost two decades he contributes to open-source, i.e. FirebirdClient. He works as a senior software engineer for Microsoft. Frequent speaker and blogger at www.tabsoverspaces.com.