Breaking changes
Every break in 7.0.0, the compiler diagnostic it produces, and whether a code fix handles it.
This is the reference list. For the upgrade itself, with an agent prompt and the order to work in, go to 6.x to 7.0.0 or 5.x to 7.0.0.
Start with the silent ones
Three changes leave your code compiling and change what it does. Nothing in your build output will mention them, so they are the only part of this upgrade you have to go looking for.
A delegate passed to Map, MapAsync, Reduce or ReduceAsync on an Option returns null
Throws ArgumentNullException, naming the delegate parameter — map or reduce. In 6.x the null was carried into the Some.
Any projection returning a nullable reference, a FirstOrDefault, a dictionary lookup, or an explicit return null
A factory passed to AndThen or AndThenAsync returns a null Option or Result
Throws ArgumentNullException, naming optionFactory or resultFactory
Factories that can return a null monad, usually from a field or a cache
A MonadOptionsScope is disposed when it is not the innermost open scope
Nothing is restored, and the library writes a ScopeDisposedOutOfOrder diagnostic event. In 6.x it restored the wrong options silently.
Any scope disposed by hand rather than with using, or held in a field
The first two throw where 6.x carried a null onward. That is the intended fix — the null was going to surface later as a NullReferenceException from code that had every right to assume a Some held a value. To map a null onto a None, use AndThen with Option.FromNullable.
The third has its own section on Configuration, and the event is on Observability.
The loud ones
These break the build. The compiler finds them for you; the table tells you what the diagnostic actually means, because several of them name something other than the real problem.
Parameter names across Option, Result and their case types
MapOr(default: x, …) → MapOr(defaultValue: x, …), and the same for else → valueFactory, createDefault → defaultFactory, createOther → optionFactory / resultFactory
CS1739
Yes
Implicit conversions to Option and Result removed
Option<int> o = 5; → Option.Some(5)
CS0029, CS1503
Yes
Per-family extension classes collapsed into one class per monad
AndThenExtensions.AndThenAsync(o, f) → o.AndThenAsync(f)
CS0103, or CS0234 on a using static
No
MonadOptions authoring moved to MonadOptionsBuilder
Use… methods are on the builder
CS1061, CS1503, CS0029, CS0103
No
MonadOptions.UseExceptionLogger removed
UseLogger, UseLoggerFactory or UseLoggerFactoryFrom from Waystone.Monads.Extensions.Logging
CS1061
No
ErrorCode.FromEnum removed
[ErrorCodeCatalog] and the generated ToErrorCode()
CS0117
No
Error.FromEnum removed
[ErrorCodeCatalog] and the generated {Enum}Catalog.Errors.{Member}(message)
CS0117
No
ErrorCodeFactory.FromEnum virtual removed
[ErrorCodeCatalog]; enum codes are settled at compile time now
CS0115
No
Result.Err<TOk>(Enum, string) overload removed
Result.Err(code.ToError(message))
CS1501
No
An async chaining step's delegate returns Task
Return ValueTask, or wrap it in an async lambda
CS0411, plus WM2022
Yes, the wrap
TryAsync and CollectAsync return ValueTask
Task<Option<T>> t = Option.TryAsync(f); → add .AsTask(), or keep the await
CS0029, or CS1503 passing it to Task.WhenAll
No
The four FromEnum-family removals and UseExceptionLogger were all obsolete in 6.x with a message naming the replacement. Nothing in 7.0.0 is removed without that warning release, with one exception below.
Where a code fix exists, run it first
Waystone.Monads ships fixes for the three rows marked Yes. They handle the bulk of a real upgrade, and running them before you edit anything by hand keeps the diff small.
The two keyed to CS1739, CS0029 and CS1503 attach to the compiler diagnostic, not to an analyzer rule — so dotnet format analyzers cannot apply them. They appear on the error in your IDE, where Fix all occurrences in Project applies them in a batch. The WM2022 fix is an ordinary analyzer fix and works either way. The upgrade pages say this again in context.
The one removal with no warning release
The implicit conversions to Option and Result were removed without being obsoleted first, which is not how this repository normally treats public API.
The reason is mechanical rather than a decision to move fast: an obsoleted implicit conversion still takes part in overload resolution. Marking it [Obsolete] would have produced a warning and left the conversion working — so the silent wrong-branch behaviour the removal exists to prevent would have carried on for another major version. There was no ordering that gave both a warning and a fix.
The extension-class collapse has the same shape for a different reason: two static classes declaring the same extension member for the same receiver is CS0121, so the old and new spellings could not coexist for a version.
Diagnostics that mask other diagnostics
Three things fire at the declaration phase, and a declaration error stops the compiler reporting body errors in the same project. So your first green build is not the whole job.
CS0234 on a using static. A missing type name blocks overload resolution in every file that has the using, so parameter renames and conversion errors in those files go unreported until you fix the qualifier.
CS0115 on an ErrorCodeFactory.FromEnum override. A codebase with both an override and call sites sees only the override error, fixes it, and then discovers the call sites on the next build.
CS0246 on a signature naming ValidationErr. Only if you install Waystone.Monads.FluentValidation, covered below. A field, property or return type spelled Result<T, ValidationErr> is a declaration error, so the bodies that call Validate stay quiet until you change the signature.
Build, fix, and build again. Twice is not paranoia here.
Waystone.Monads.FluentValidation
This companion package was rewritten in 7.0.0. If you do not install it, skip this section. If you do, it is a clean break rather than a deprecation, so nothing warned you in 6.x.
ValidationErr wrapped a failed ValidationResult and converted to an Error when you asked. That meant Result<TValue, ValidationErr> could not join a chain without a MapErr at the seam, and ToError() read your configuration at the moment you called it — so the same failure produced a different code depending on where you converted it.
ValidationError replaces it and is an Error.
ValidationErr removed
ValidationError, which derives from Error
CS0246
Validate and ValidateAsync err with Error
Result<T, ValidationErr> → Result<T, Error>
CS0029
ValidateAsync returns ValueTask
Add .AsTask(), or keep the await
CS0029, CS1503
ValidationErr.ToError() removed
Nothing to call — you already have an Error
CS1061
ValidationErr.Create() removed
Validate or ValidateAsync
CS0117
AsValidationResult() and RuleSetsExecuted removed
Use Failures for the failure list
CS1061
UseFallbackValidationErrorMessage removed
Nothing — the case it covered cannot happen now
CS1061
Namespaces shadow FluentValidation's own
Waystone.Monads.FluentValidation.Results.Extensions → FluentValidation.Extensions
CS0246, CS0234
There is no code fix for any of these.
The namespaces shadow FluentValidation's own
The types moved out of Waystone.Monads.FluentValidation.* and into FluentValidation.*, so they sit beside IValidator and ValidationFailure in the namespace your validator file already imports.
Waystone.Monads.FluentValidation.Results
FluentValidation
Waystone.Monads.FluentValidation.Results.Extensions
FluentValidation.Extensions
Waystone.Monads.FluentValidation.Configs
FluentValidation.Configs
The package and assembly keep the name Waystone.Monads.FluentValidation, so the PackageReference does not change. Delete the old using directives; a file that already has using FluentValidation; needs nothing back for ValidationError.
No shim ships in the old namespace. A forwarding type there would be a distinct type at run time, so error is ValidationError would compile against it and silently stop matching — a quiet wrong answer in place of a build error.
Why the removals had no warning release
ValidationErr could not be obsoleted alongside its replacement. The new Validate differs from the old one only in its return type, and C# cannot overload on that. The two spellings could only coexist in separate namespaces, which would give CS0121 to anyone importing both.
Why UseFallbackValidationErrorMessage is gone rather than renamed
It set the message used when a validation failure carried none of its own. A ValidationError now has an internal constructor that only the failure branch reaches, so it always carries at least one failure. There is no empty case left for a fallback to cover, and Error already substitutes the core fallback for a blank message.
Recovering the failure detail
Where you used to hold a ValidationErr, you now pattern match:
Failures carries the ValidationFailure list, and ToDictionary() groups the messages by property, exactly as before. Full detail is on Waystone.Monads.FluentValidation.
The supported FluentValidation range widened
The package now declares FluentValidation >= 11.1.0 && < 13.0.0, where 6.x pinned a single version. 11.1.0 is the first release carrying ValidationResult.ToDictionary() as an instance method.
Rule ids that no longer exist
WM2010 is retired in 7.0.0. It reported a Result<T, T> whose two implicit conversions were ambiguous, and there are no implicit conversions left for it to report on.
Retired ids are never reused, so a stale .editorconfig entry or #pragma naming one does nothing at all — it does not error, and it does not warn. The full list of retired ids is on Deprecations.
Last updated
Was this helpful?