Skip to content

Reflection & Assemblies

Mehdi Hadeli edited this page Jan 17, 2023 · 26 revisions

Articles

Notes

  • AppDomain contains loaded assemblies by using AssemblyLoadContext.Default, but if AppDomain assemblies have a reference to other assemblies they are lazy, and it loads just needed assemblies.
  • Assemblies are lazy loaded, this means they load just assemblies they need (for example in startup). So using AppDomain.GetAssemblies() is not reliable (it is possible to get ReflectionTypeLoadException, because some dependent type assembly are lazy and not loaded yet) but sometimes it works, and we are lucky.
  • in Xunit and with dotnet test runner instead of loading just needed assemblies lazily, it loads all DLLs in the bin (shadow copy) but in the rider test runner it works fine, and it just loads needed assemblies to AppDomain, with this side effect and using AppDomiain.CurrentDomain.GetAllAsseblies() we have more assemblies and that is possible some of their internal dependencies not loaded in the startup (not touched yet), and it is possible to get ReflectionTypeLoadException. Because reference assemblies have some lazy references assemblies, this means that if we call it during bootstrap, and we haven’t touched at least one type per Assembly, their referenced assembly will not load until they are used (by calling a method on these reference assemblies).

One example for this is Hypothesist.MassTransit assemblies which is load with its dependent assemblies doesn't load, and we get ReflectionTypeLoadException because it can't find correspond dependent Assembly version MassTransit. Might we don't get this error in rider because rider just load needed assemblies as lazily and in startup it doesn't touch Hypothesist.MassTransit yet. It is better to use GetReferencedAssemblies

AppDomain.CurrentDomain.GetAssemblies().ToArray();

Assemblies load by AppDomain based on their code path and their code usage. Assembly will add to AppDomain dynamically by Jit when visit one of assembly class method.

We could load all references assembly with using this helper function, and load all references assemblies explicitly with Assembly.Load(reference):

ReflectionUtilities.GetAllReferencedAssemblies(Assembly.GetCallingAssembly()).ToArray();

public static IEnumerable<Assembly> GetAllReferencedAssemblies(Assembly? rootAssembly)
{
	var list = new HashSet<string>();
	var stack = new Stack<Assembly?>();

	var root = rootAssembly ?? Assembly.GetEntryAssembly();
	stack.Push(root);

	do
	{
		var asm = stack.Pop();

		if (asm == null)
			break;

		yield return asm;

		foreach (var reference in asm.GetReferencedAssemblies())
		{
			if (!list.Contains(reference.FullName))
			{
				// loading assemblies explicitly, because assemblies are loaded lazily
				stack.Push(Assembly.Load(reference));
				list.Add(reference.FullName);
			}
		}
	}
	while (stack.Count > 0);
}
Clone this wiki locally