fix: resolve System.Memory version conflict on .NET Framework (net462)#5303
fix: resolve System.Memory version conflict on .NET Framework (net462)#5303
Conversation
Add explicit System.Memory 4.6.3 package reference to NuGet tester projects when targeting .NET Framework. The conflict arose because transitive dependencies resolved System.Memory 4.5.5 (assembly 4.0.1.2 via net461) and 4.6.3 (assembly 4.0.2.0 via netstandard2.0) through different paths, causing a FileLoadException at runtime. Fixes #5297
There was a problem hiding this comment.
Code Review
Summary: This PR fixes a assembly version conflict on .NET Framework targets (net462) that caused a at runtime.
Analysis
The root cause is well-diagnosed: two transitive resolution paths produce different assembly versions ( via net461 and via netstandard2.0), causing MSBuild to pick the wrong one as "primary". The fix forces a unified resolution by pinning an explicit reference to 4.6.3.
What's Good
-
Correct use of CPM (Central Package Management): Adding to
Directory.Packages.propsdefines the version centrally without leaking it to other projects — only projects that explicitly reference the package will get it. -
Correct condition syntax: Using
'$(TargetFrameworkIdentifier)' == '.NETFramework'is the right idiom for multi-targeted projects. It's more robust than checking$(TargetFramework)directly (which would only match exact TFM strings likenet462, notnet45etc.). -
Minimal blast radius: Changes are scoped to only the NuGet tester projects where the conflict manifests, and only for .NET Framework targets.
-
Consistent application: All three language variants (C#, F#, VB.NET) are updated identically.
Minor Observation
The alternative fix — using <BindingRedirect> in an app.config — would be more brittle and harder to maintain across three projects. The chosen approach of an explicit package reference is the correct MSBuild solution for this class of problem.
One thing worth noting: the explicit reference resolves to assembly version 4.0.5.0 (net462 lib) rather than unifying at 4.0.2.0 — this is a "superset" unification that may silently satisfy both dependency paths. If a future System.Memory version reintroduces a breaking change for net462, this conditional reference ensures only .NET Framework consumers are affected.
Verdict
Clean, targeted fix with a thorough root cause analysis. The approach is correct and the implementation is consistent. No issues found.
There was a problem hiding this comment.
Code Review
Summary: This PR fixes a System.Memory assembly version conflict on .NET Framework targets (net462) that caused a FileLoadException at runtime.
Analysis
The root cause is well-diagnosed: two transitive resolution paths produce different assembly versions (4.0.1.2 via net461 and 4.0.2.0 via netstandard2.0), causing MSBuild to pick the wrong one as "primary". The fix forces unified resolution by pinning an explicit reference to 4.6.3.
What's Good
-
Correct use of CPM (Central Package Management): Adding to
Directory.Packages.propsdefines the version centrally without leaking it to other projects — only projects that explicitly reference the package will get it. -
Correct condition syntax: Using
'$(TargetFrameworkIdentifier)' == '.NETFramework'is the right idiom for multi-targeted projects. It's more robust than checking$(TargetFramework)directly (which would only match exact TFM strings likenet462, notnet45etc.). -
Minimal blast radius: Changes are scoped to only the NuGet tester projects where the conflict manifests, and only for .NET Framework targets.
-
Consistent application: All three language variants (C#, F#, VB.NET) are updated identically.
Minor Observation
The alternative fix — using <BindingRedirect> in an app.config — would be more brittle and harder to maintain across three projects. The chosen approach of an explicit package reference is the correct MSBuild solution for this class of problem.
Worth noting: the explicit reference resolves to assembly version 4.0.5.0 (net462 lib), which is a "superset" unification that satisfies both dependency paths. If a future System.Memory version reintroduces a breaking change for net462, the conditional reference ensures only .NET Framework consumers are affected.
Verdict
Clean, targeted fix with thorough root cause analysis. The approach is correct and the implementation is consistent. No issues found. ✓
Updated [TUnit](https://github.com/thomhurst/TUnit) from 1.23.7 to 1.24.13. <details> <summary>Release notes</summary> _Sourced from [TUnit's releases](https://github.com/thomhurst/TUnit/releases)._ ## 1.24.13 <!-- Release notes generated using configuration in .github/release.yml at v1.24.13 --> ## What's Changed ### Other Changes * perf(mocks): optimize MockEngine for lower allocation and faster verification by @thomhurst in thomhurst/TUnit#5319 * Remove defunct `UseTestingPlatformProtocol` reference for vscode by @erwinkramer in thomhurst/TUnit#5328 * perf(aspnetcore): prevent thread pool starvation during parallel WebApplicationTest server init by @thomhurst in thomhurst/TUnit#5329 * fix TUnit0073 for when type from from another assembly by @SimonCropp in thomhurst/TUnit#5322 * Fix implicit conversion operators bypassed in property injection casts by @Copilot in thomhurst/TUnit#5317 * fix(mocks): skip non-virtual 'new' methods when discovering mockable members by @thomhurst in thomhurst/TUnit#5330 * feat(mocks): IFoo.Mock() discovery with generic fallback and ORP resolution by @thomhurst in thomhurst/TUnit#5327 ### Dependencies * chore(deps): update tunit to 1.24.0 by @thomhurst in thomhurst/TUnit#5315 * chore(deps): update aspire to 13.2.1 by @thomhurst in thomhurst/TUnit#5323 * chore(deps): update verify to 31.14.0 by @thomhurst in thomhurst/TUnit#5325 ## New Contributors * @erwinkramer made their first contribution in thomhurst/TUnit#5328 **Full Changelog**: thomhurst/TUnit@v1.24.0...v1.24.13 ## 1.24.0 <!-- Release notes generated using configuration in .github/release.yml at v1.24.0 --> ## What's Changed ### Other Changes * perf: optimize TUnit.Mocks hot paths by @thomhurst in thomhurst/TUnit#5304 * fix: resolve System.Memory version conflict on .NET Framework (net462) by @thomhurst in thomhurst/TUnit#5303 * fix: resolve CS0460/CS0122/CS0115 when mocking concrete classes from external assemblies by @thomhurst in thomhurst/TUnit#5310 * feat(mocks): parameterless Returns() and ReturnsAsync() for async methods by @thomhurst in thomhurst/TUnit#5309 * Fix typo in NUnit manual migration guide by @aa-ko in thomhurst/TUnit#5312 * refactor(mocks): unify Mock.Of<T>() and Mock.OfPartial<T>() into single API by @thomhurst in thomhurst/TUnit#5311 * refactor(mocks): clean up Mock API surface by @thomhurst in thomhurst/TUnit#5314 * refactor(mocks): remove generic/untyped overloads from public API by @thomhurst in thomhurst/TUnit#5313 ### Dependencies * chore(deps): update tunit to 1.23.7 by @thomhurst in thomhurst/TUnit#5305 * chore(deps): update dependency mockolate to 2.1.1 by @thomhurst in thomhurst/TUnit#5307 ## New Contributors * @aa-ko made their first contribution in thomhurst/TUnit#5312 **Full Changelog**: thomhurst/TUnit@v1.23.7...v1.24.0 Commits viewable in [compare view](thomhurst/TUnit@v1.23.7...v1.24.13). </details> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Summary
System.Memory 4.6.3package reference to NuGet tester projects (C#, F#, VB.NET) for .NET Framework targetsSystem.MemorytoDirectory.Packages.propsfor central package managementRoot Cause
The NuGet tester projects target both .NET Framework (net462) and .NET Core. They reference
TUnit.NugetTester.Library(netstandard2.0), which creates two NuGet resolution paths forSystem.Memory:MSBuild picks
4.0.1.2as "primary" and copies that DLL. At runtime, code compiled against4.0.2.0fails withFileLoadException.Adding an explicit
System.Memory 4.6.3reference makes it the primary, resolving to assembly version4.0.5.0(net462 lib) which unifies both.Test plan
modularpipeline (windows-latest)with net462 targetFixes #5297