I have a Visual Studio 2023 solution containing 32 projects. The projects extensively reference each other. 31 of the projects are class libraries, while the last one is a console application used whenever I want to test one of the functions in my library, so I don't have to create a new temporary one each time. Visual Studio's Immediate Window has never worked for me, even before this issue. All the projects are written in C# 11, and use .NET 7.0. Many of the projects reference the NuGet package System.Collections.Immutable
.
I was attempting to write a Crawler, like a Web Crawler, but for types. It would iterate though all static/nonstatic members of a type, add their parameter types and return types (if present) to a HashSet<Type>
. It would then recursively do the same for each type that was added. It was designed so that it would only ever iterate though each time once.
It did this by creating a List<Type>
, and every time a type was added to the HashSet<Type>
that did not already exist, it would add it to the list. After the list was complete, it would recursively do the same for each type in the list. If a type was, or contained in its generic parameters (if present), a generic parameter, it would be substituted with its definition. There were also some functions to cover pointers, references, and arrays.
I ran the function on an object, and it worked fine. I got the type for the object using C#'s typeof(?)
method. However, then I ran the function on a static class in one of my projects, and it threw an exception.
At this point, it is worth mentioning the projects that are involved with this. I have one console application project (Test
) in which I am executing the crawler in a second project (Brendan.IC.Typed.Execution
), which was supposed to crawl through a static class in a third project (Brendan.IC.Typed
). The latter project references System.Collections.Immutable
. System.Collections.Immutable
most likely has nothing to do with this, but I think its worth mentioning everything, since I have absolutely no idea what the problem is.
The exception was a System.TypeLoadException
.
System.TypeLoadException: 'Could not load type 'Brendan.IC.Typed.TypedEvolutionLookup`1' from assembly 'Brendan.IC.Typed, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.'
The static class I had called the crawler on was Brendan.IC.Typed.TypedEvolution
. One of its static methods contained a parameter of type Brendan.IC.Typed.TypedEvolutionLookup<>
. This is how the crawler found it. It then was unable to load it. The exception was thrown in the MethodInfo.GetParameters()
function.
Initially, I assumed this was some problem with my crawler function. I cleaned and rebuilt the solution, my normal first step in handling errors, but to no effect. I recompiled the solution multiple times in both Debug
and Release
mode, to no effect. I checked my code for anything that might effect type loading. I found nothing. Finally, I decided to load the offending type in my top-level statements. It threw an exception, the same exception it had previously.
System.TypeLoadException: 'Could not load type 'CodeInterpretation.Typed.TypedEvolutionLookup`1' from assembly 'Brendan.IC.Typed, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.'
Here was my top-level statement:
using Brendan.IC.Typed;
using Brendan.IC.Typed.Execution;
using static System.Console;
Type bigType = typeof(TypedEvolutionLookup<int>);
Crawler.Crawl(bigType);
ReadKey(true);
I simplified it to this:
using Brendan.IC.Typed;
using static System.Console;
Type bigType = typeof(TypedEvolutionLookup<int>);
ReadKey(true);
This is when things started to get weird... real weird.
First of all, I noticed that there was no line number or call stack associated with the exception.
Upon selecting Show Call Stack:
Here's my full screen:
Usually, when I get an exception, I can add a WriteLine() right before the line I suspect, and it will print to the console before the program terminates due to the exception. I did so, and added a breakpoint on the same line, intending to step through the program. Finally, I added a generic parameter to the generic type definition. But the program didn't even hit the breakpoint! Nor did it print to the console!
New code:
using Brendan.IC.Typed;
using static System.Console;
WriteLine("hi");
Type bigType = typeof(TypedEvolutionLookup<int>);
ReadKey(true);
Screenshot:
I put it in release mode, too. As expected, it did not throw an error, since it optimized out the unnecessary assignment. Nor did it hit the breakpoint, obviously.
I removed the 4th line, and it then work fine. I added it back in, it broke. I tried getting a different type from the Brendan.IC.Typed
assembly. It worked fine. I replaced the WriteLine()
statement with another ReadKey(true)
statement. It broke.
using Brendan.IC.Typed;
using static System.Console;
ReadKey(true);
Type bigType = typeof(TypedEvolutionLookup<int>);
ReadKey(true);
I got rid of the generic parameter. There was no change:
What worries me is that I think the error is happening, not with the execution of the typeof
statement, but in the compiler for the IL! The error is being thrown between when I compile it, and when the top-level statements are executed! There is only one thing I can think of that would fit those constraints, and depend on the existence of a typeof
line, and that it the IL runtime compiler itself!!! Yet the type is being found easily by the C#-to-IL compiler!
And the error is not happening with any of the other types in the assembly! Unfortunately, the TypedEvolutionLookup<>
struct is the only generic type in the Brendan.IC.Typed
assembly. However, it is not the only readonly struct. I have, though, tested some of the other readonly structs, and they do not have the same problem. The IL compiler seems to, for some reason, single out this one specific type, and, dependent or not on generics, this should not be happening.
I have, multiple times, restarted my computer, in case there is some secret RAM type-cache that I am not aware of. I have, multiple times, cleaned and rebuilt the solution, in case there is something weird with that. I have, multiple times, deleted the output directories of all my projects, in case some past versions are interfering with the present ones.
(All my class libraries compile to the same output directory; however, the console application compiles to its own, so as to avoid confusion.)
There has been no change. I do not understand this. I want to get the System.Type
for this struct. Why has the IL compiler singled out this specific one? It has no funky attributes, except for the occasional [MaybeNullWhen(false)]
in the parameters of its methods.
Why could this be happening, and how might I avoid it?