Recently I’ve had reasons to dive deep into how .NET does dependency resolution. In a real world application with transitive dependencies the same package can be referenced for multiple reasons. To make things more complex, the dependencies of a .NET assembly are specified per target framework. It turns out that this can create a situation where a solution compiles perfectly well just to blow up with a System.MissingMethodException during runtime.
Consider the following solution:
- App: TargetFramework net10.0, references Lib8
- Lib8: TargetFramework net8.0, references Lib10
- Lib10: TargetFrameworks net8.0, net10.0.
Now, this solution compiles perfectly well. Lib10 is compiled twice, once for each target framework. Lib8 compiles just fine referencing the .NET 8 version of Lib10. App compiles just fine referencing the .NET 8 version of Lib8. The interesting thing happens on runtime though: The assembly loaded of Lib10 depends on the target framework of the hosting app, not of the referencing library. I made a quick test to prove that. This is the contents of my Lib10:
public static class Class10 { #if NET8_0 public static void Hello() => WriteLine("Hello 10 running in .NET 8"); #endif #if NET10_0 public static void Hello() => WriteLine("Hello 10 actually running in .NET 10"); #endif } |
Running the app yields this result:
Hello app Hello 8 Hello 10 actually running in .NET 10 |
This clearly shows that it’s the .NET 10 version of the Lib10 that is running. Let’s take it one step further and actually remove the .NET 10 implementation from the code:
public static class Class10 { #if NET8_0 public static void Hello() => WriteLine("Hello 10 running in .NET 8"); #endif } |
Now our Lib10 only has an implementation when run in .NET 8. The solution still builds because the compile time dependency of Lib8 is the .NET 8 version of Lib10. When running however:
Hello app Unhandled exception. System.MissingMethodException: Method not found: 'Void Lib10.Class10.Hello()'. at Lib8.Class8.Hello() at Lib8.Class8.Hello() at Program.<main>$(String[] args) in D:\temp\TargertResolutionTest\App\Program.cs:line 5 |
The reason I didn’t know of this before is probably because it’s very rare that the public API surface is different from different target framework versions. Let me know in the comments if you’ve come across this situation in the wild!