Skip to content

fix: resolve System.Memory version conflict on .NET Framework (net462)#5303

Merged
thomhurst merged 1 commit intomainfrom
fix/fsharp-nuget-tester-system-memory
Mar 30, 2026
Merged

fix: resolve System.Memory version conflict on .NET Framework (net462)#5303
thomhurst merged 1 commit intomainfrom
fix/fsharp-nuget-tester-system-memory

Conversation

@thomhurst
Copy link
Copy Markdown
Owner

Summary

  • Add explicit System.Memory 4.6.3 package reference to NuGet tester projects (C#, F#, VB.NET) for .NET Framework targets
  • Add System.Memory to Directory.Packages.props for central package management

Root 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 for System.Memory:

Path Package Assembly Version
Direct transitive (net461 lib) 4.5.5 4.0.1.2
Via Library (netstandard2.0 lib) 4.6.3 4.0.2.0

MSBuild picks 4.0.1.2 as "primary" and copies that DLL. At runtime, code compiled against 4.0.2.0 fails with FileLoadException.

Adding an explicit System.Memory 4.6.3 reference makes it the primary, resolving to assembly version 4.0.5.0 (net462 lib) which unifies both.

Test plan

  • Verified all three tester projects (C#, F#, VB.NET) build for net462 with no MSB3277 warnings
  • CI passes for modularpipeline (windows-latest) with net462 target

Fixes #5297

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
Copy link
Copy Markdown
Contributor

@claude claude bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.props defines 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 like net462, not net45 etc.).

  • 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.

Copy link
Copy Markdown
Contributor

@claude claude bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.props defines 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 like net462, not net45 etc.).

  • 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. ✓

@thomhurst thomhurst merged commit 67d0bc9 into main Mar 30, 2026
16 checks passed
@thomhurst thomhurst deleted the fix/fsharp-nuget-tester-system-memory branch March 30, 2026 11:01
This was referenced Mar 30, 2026
github-actions bot pushed a commit to IntelliTect/CodingGuidelines that referenced this pull request Apr 6, 2026
Updated [TUnit.Core](https://github.com/thomhurst/TUnit) from 1.23.7 to
1.28.7.

<details>
<summary>Release notes</summary>

_Sourced from [TUnit.Core's
releases](https://github.com/thomhurst/TUnit/releases)._

## 1.28.7

<!-- Release notes generated using configuration in .github/release.yml
at v1.28.7 -->

## What's Changed
### Other Changes
* fix: prevent StringBuilder race in console interceptor during parallel
tests by @​thomhurst in thomhurst/TUnit#5414
### Dependencies
* chore(deps): update tunit to 1.28.5 by @​thomhurst in
thomhurst/TUnit#5415


**Full Changelog**:
thomhurst/TUnit@v1.28.5...v1.28.7

## 1.28.5

<!-- Release notes generated using configuration in .github/release.yml
at v1.28.5 -->

## What's Changed
### Other Changes
* perf: eliminate redundant builds in CI pipeline by @​thomhurst in
thomhurst/TUnit#5405
* perf: eliminate store.ToArray() allocation on mock behavior execution
hot path by @​thomhurst in thomhurst/TUnit#5409
* fix: omit non-class/struct constraints on explicit interface mock
implementations by @​thomhurst in
thomhurst/TUnit#5413
### Dependencies
* chore(deps): update tunit to 1.28.0 by @​thomhurst in
thomhurst/TUnit#5406


**Full Changelog**:
thomhurst/TUnit@v1.28.0...v1.28.5

## 1.28.0

<!-- Release notes generated using configuration in .github/release.yml
at v1.28.0 -->

## What's Changed
### Other Changes
* fix: resolve build warnings in solution by @​thomhurst in
thomhurst/TUnit#5386
* Perf: Optimize MockEngine hot paths (~30-42% faster) by @​thomhurst in
thomhurst/TUnit#5391
* Move Playwright install into pipeline module by @​thomhurst in
thomhurst/TUnit#5390
* perf: optimize solution build performance by @​thomhurst in
thomhurst/TUnit#5393
* perf: defer per-class JIT via lazy test registration + parallel
resolution by @​thomhurst in
thomhurst/TUnit#5395
* Perf: Generate typed HandleCall<T1,...> overloads to eliminate
argument boxing by @​thomhurst in
thomhurst/TUnit#5399
* perf: filter generated attributes to TUnit-related types only by
@​thomhurst in thomhurst/TUnit#5402
* fix: generate valid mock class names for generic interfaces with
non-built-in type args by @​thomhurst in
thomhurst/TUnit#5404
### Dependencies
* chore(deps): update tunit to 1.27.0 by @​thomhurst in
thomhurst/TUnit#5392
* chore(deps): update dependency path-to-regexp to v8 by @​thomhurst in
thomhurst/TUnit#5378


**Full Changelog**:
thomhurst/TUnit@v1.27.0...v1.28.0

## 1.27.0

<!-- Release notes generated using configuration in .github/release.yml
at v1.27.0 -->

## What's Changed
### Other Changes
* Fix Dependabot security vulnerabilities in docs site by @​thomhurst in
thomhurst/TUnit#5372
* fix: use 0.0.0-scrubbed sentinel version in snapshot scrubber to avoid
false Dependabot alerts by @​thomhurst in
thomhurst/TUnit#5374
* Speed up Engine.Tests by removing ProcessorCount parallelism cap by
@​thomhurst in thomhurst/TUnit#5379
* ci: add concurrency groups to cancel redundant workflow runs by
@​thomhurst in thomhurst/TUnit#5373
* Add scope-aware initialization and disposal OpenTelemetry spans to
trace timeline and HTML report by @​Copilot in
thomhurst/TUnit#5339
* Add WithInnerExceptions() for fluent AggregateException assertion
chaining by @​thomhurst in thomhurst/TUnit#5380
* Drop net6.0 and net7.0 TFMs, keep net8.0+ and netstandard2.x by
@​thomhurst in thomhurst/TUnit#5387
* Remove all [Obsolete] members and migrate callers by @​thomhurst in
thomhurst/TUnit#5384
* Add AssertionResult.Failed overload that accepts an Exception by
@​thomhurst in thomhurst/TUnit#5388
### Dependencies
* chore(deps): update dependency mockolate to 2.3.0 by @​thomhurst in
thomhurst/TUnit#5370
* chore(deps): update tunit to 1.25.0 by @​thomhurst in
thomhurst/TUnit#5371
* chore(deps): update dependency minimatch to v9.0.9 by @​thomhurst in
thomhurst/TUnit#5375
* chore(deps): update dependency path-to-regexp to v0.2.5 by @​thomhurst
in thomhurst/TUnit#5376
* chore(deps): update dependency minimatch to v10 by @​thomhurst in
thomhurst/TUnit#5377
* chore(deps): update dependency picomatch to v4 by @​thomhurst in
thomhurst/TUnit#5382
* chore(deps): update dependency svgo to v4 by @​thomhurst in
thomhurst/TUnit#5383
* chore(deps): update dependency path-to-regexp to v1 [security] by
@​thomhurst in thomhurst/TUnit#5385


**Full Changelog**:
thomhurst/TUnit@v1.25.0...v1.27.0

## 1.25.0

<!-- Release notes generated using configuration in .github/release.yml
at v1.25.0 -->

## What's Changed
### Other Changes
* Fix missing `default` constraint on explicit interface implementations
with unconstrained generics by @​thomhurst in
thomhurst/TUnit#5363
* feat(mocks): add ReturnsAsync typed factory overload with method
parameters by @​thomhurst in
thomhurst/TUnit#5367
* Fix Arg.IsNull<T> and Arg.IsNotNull<T> to support nullable value types
by @​thomhurst in thomhurst/TUnit#5366
* refactor(mocks): use file-scoped types for generated implementation
details by @​thomhurst in thomhurst/TUnit#5369
* Compress HTML report JSON data and minify CSS by @​thomhurst in
thomhurst/TUnit#5368
### Dependencies
* chore(deps): update tunit to 1.24.31 by @​thomhurst in
thomhurst/TUnit#5356
* chore(deps): update dependency mockolate to 2.2.0 by @​thomhurst in
thomhurst/TUnit#5357
* chore(deps): update dependency polyfill to 9.24.1 by @​thomhurst in
thomhurst/TUnit#5365
* chore(deps): update dependency polyfill to 9.24.1 by @​thomhurst in
thomhurst/TUnit#5364


**Full Changelog**:
thomhurst/TUnit@v1.24.31...v1.25.0

## 1.24.31

<!-- Release notes generated using configuration in .github/release.yml
at v1.24.31 -->

## What's Changed
### Other Changes
* Fix Aspire 13.2.0+ timeout caused by ProjectRebuilderResource being
awaited by @​Copilot in thomhurst/TUnit#5335
* chore(deps): update dependency polyfill to 9.24.0 by @​thomhurst in
thomhurst/TUnit#5349
* Fix nullable IParsable type recognition in source generator and
analyzer by @​Copilot in thomhurst/TUnit#5354
* fix: resolve race condition in HookExecutionOrderTests by @​thomhurst
in thomhurst/TUnit#5355
* Fix MaxExternalSpansPerTest cap bypass when Activity.Parent chain is
broken by @​Copilot in thomhurst/TUnit#5352
### Dependencies
* chore(deps): update tunit to 1.24.18 by @​thomhurst in
thomhurst/TUnit#5340
* chore(deps): update dependency stackexchange.redis to 2.12.14 by
@​thomhurst in thomhurst/TUnit#5343
* chore(deps): update verify to 31.15.0 by @​thomhurst in
thomhurst/TUnit#5346
* chore(deps): update dependency polyfill to 9.24.0 by @​thomhurst in
thomhurst/TUnit#5348


**Full Changelog**:
thomhurst/TUnit@v1.24.18...v1.24.31

## 1.24.18

<!-- Release notes generated using configuration in .github/release.yml
at v1.24.18 -->

## What's Changed
### Other Changes
* feat(mocks): shorter, more readable generated mock type names by
@​thomhurst in thomhurst/TUnit#5334
* Fix DisposeAsync() ordering for nested property injection by @​Copilot
in thomhurst/TUnit#5337
### Dependencies
* chore(deps): update tunit to 1.24.13 by @​thomhurst in
thomhurst/TUnit#5331


**Full Changelog**:
thomhurst/TUnit@v1.24.13...v1.24.18

## 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.28.7).
</details>

[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=TUnit.Core&package-manager=nuget&previous-version=1.23.7&new-version=1.28.7)](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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FSharp NuGet tester fails on Windows net462: System.Memory version conflict

1 participant