Can we safely reference .NET8 libraries from .NET9 apps?

With each upgrade of the .NET platform, developers may decide whether to upgrade or wait until the rest of the ecosystem upgrades first. Here is my experience when I wanted to understand if it’s ok to bump up my projects to .NET 9 while some of the NuGet libraries I depend on are still on .NET 8.

The theory: .NET versions are highly backward-compatible

In theory, each subsequent version of .NET preserves a high degree of compatibility with the previous versions, including binary compatibility in most scenarios, so we can use packages compiled with older versions of .NET tooling.

Compatibility is high but not 100%, and Microsoft publishes the documentation listing breaking changes for each platform version (e.g., the breaking changes introduced in .NET 9).

I believe having breaking changes is best because the platform couldn’t evolve without them from time to time, but let’s see how they can affect our programs when we decide to upgrade.

Experiment: referencing NET8 library from NET9 program

I created a small demo app to show what happens if we reference a .NET 8 library that uses a BinaryFormatter API removed in .NET 9.

In the .NET library, I have two methods:

  • public static int Add(int a, int b) => a+b;
    This one has no breaking changes and should be compatible with all .NET versions.
  • public static void UseBinaryFormatter() (...)
    This one internally uses the BinaryFormatter class removed in net9. It is one of the known binary incompatibilities between net8 and net9.

Here’s how the program behaves:

The code compiled and mostly worked, but I was able to cause an exception rooted in breaking changes between versions

The key observations are that:

  1. The program allowed references from the dotnet9 program to the dotnet8 library without complaining. The NET 9 part of the code did not contain warnings.
  2. There is a warning in the NET8 library code that BinaryFormatter is obsolete, but we only see it if the library is part of our solution and not from a third party.
  3. The program can be run and execute the compatible part of code just fine, even though the same class uses incompatible types in other places.
  4. The program only fails at runtime when it reaches code that is no longer compatible with .NET 9. So, we risk detecting problems in our app late in the development process as such bugs are unlikely to appear in the IDE as warnings or compilation errors.

Conclusions

Mixing .NET versions in a project comes with risks that might be missed at compile time and only manifest at runtime. They are rare, and there is a high chance that everything will work fine, but to have confidence that a program will work reliably, our tests must have enough coverage to catch problems, and we should have warnings under control.

I enjoy upgrading my projects on “day 0,” just after the new stable versions of .NET are released. However, assuming that the new version is not a game changer for us, a more rational approach would be to delay it for a while and let the ecosystem of dependencies adapt. When we upgrade our solutions, it would also simplify things if we updated all projects together.

No comments yet, you can leave the first one!

Leave a Comment