Deprecations
API that has been removed, the version that removed it, and what to use instead.
What this page is for
We deprecate before we delete. Anything on its way out is marked [Obsolete] first, so your build reports a CS0618 warning naming the replacement and the version that removes it. We only delete in a major release.
Everything under Pending removal is marked now and goes in the release named. Everything under Removed in has already gone.
Nothing is pending removal today. 7.0.0 carries no [Obsolete] member at all — everything that was marked in 6.x has now gone, and nothing new has been marked for 8.0.0. The next thing to appear on this page will be marked in a 7.x minor.
Removed in 7.0.0
Seeing handled exceptions through a hand-written delegate
Deprecated in: 6.7.0 · Removed in: 7.0.0 · Replacement: Waystone.Monads.Extensions.Logging
MonadOptions.UseExceptionLogger(Action<Exception, CallerInfo>)
UseLoggerFactoryFrom(IServiceProvider), UseLoggerFactory(ILoggerFactory) or UseLogger(ILogger), called on the builder inside MonadOptions.Configure
How to migrate
Install the package and hand the library your logger. Delete the old call in the same change.
dotnet add package Waystone.Monads.Extensions.Logging-MonadOptions.Configure(options => options.UseExceptionLogger((ex, caller) =>
-{
- Log.Error(ex, "Monad caught {Member}:{Line}", caller.MemberName, caller.LineNumber);
-}));
+MonadOptions.Configure(options => options.UseLoggerFactoryFrom(app.Services));Without a service provider, build a factory and pass it directly:
You lose nothing in the move. Every entry still carries the exception and the same call-site details (MemberName, ArgumentExpression and LineNumber), and you gain level and category filtering from appsettings.json, which an opaque delegate could never give you. See Observability.
If you are still on 6.x, delete the old call as you add the package. Both fire for the whole of 6.x, so leaving UseExceptionLogger in place logs every handled exception twice. On 7.0.0 the member is gone, so the double-logging window is closed — you get a CS1061 instead.
Why this changed
UseExceptionLogger holds exactly one delegate. Configure a second observer and it replaces the first, silently — so the library could never support more than one integration watching these exceptions at a time.
The library now writes each handled exception to a DiagnosticListener, which any number of subscribers can share, and publishes a counter on a meter named after itself. The package is one subscriber. Your own tooling can be another, and neither has to know the other exists.
You also had to write the delegate yourself, for whichever logging library you use. Handing us an ILogger is less code and behaves the way every other .NET library does.
Working an error code out from an enum at run time
Deprecated in: 6.2.0 · Removed in: 7.0.0 · Replacement: the members generated by [ErrorCodeCatalog]
ErrorCode.FromEnum(Enum)
MyEnumCatalog.Codes.Member, or value.ToErrorCode()
ErrorCodeFactory.FromEnum(Enum)
[ErrorCodeCatalog(Format = "…")], or [assembly: ErrorCodeFormat("…")]
Error.FromEnum(Enum, string)
MyEnumCatalog.Errors.Member(message), or value.ToError(message)
Result.Err<TOk>(Enum, string)
Result.Err<TOk>(MyEnumCatalog.Errors.Member(message))
How to migrate
Mark the enum with [ErrorCodeCatalog] and use what it generates.
Do this before you upgrade, not after. In 6.x the analyzer offered a lightbulb fix for each of these on the deprecation warning itself. In 7.0.0 the members are gone, so there is no warning to attach a fix to and the fix provider was removed with them. On 7.0.0 you get a CS0117 or CS1061 and the rewrite is yours to do.
When the member is not known where you are standing, the generated extensions take their place, and the fix produces these instead:
If you shaped your codes by subclassing ErrorCodeFactory and overriding FromEnum, say the same thing with a Format — see Source generation. FromException is not deprecated, and a factory that only overrides it needs no change.
Why this changed
Both of these work the code out by reflection at run time, which costs you three things a generated constant gives you: the compiler cannot see the code, so a renamed member changes your wire contract silently; the declared Format cannot be applied, because it is read at compile time; and the analyzers and the error code registry cannot review a string nothing in the build can see.
Removed in 7.0.0 with no deprecation window
Three things went in 7.0.0 without being marked [Obsolete] first. That is not how this page normally works, so each one says why no warning release was possible.
The implicit conversions to Option and Result
implicit operator Option<T>(T value)
Option.Some(value), or Option.FromNullable(value)
implicit operator Result<TOk, TErr>(TOk value)
Result.Ok<TOk, TErr>(value)
implicit operator Result<TOk, TErr>(TErr value)
Result.Err<TOk, TErr>(value)
Why no warning release. An [Obsolete] implicit conversion still takes part in overload resolution. Marking these would have produced a CS0618 and left the conversions working — so the silent wrong-branch behaviour they were removed to prevent would have carried on through the whole of 7.x. There was no ordering that gave both a warning and a fix.
There is a code fix, on the CS0029 or CS1503 you get instead. Where a Result carries the same type on both sides it offers both Ok and Err and does not choose for you.
The per-family extension classes
AndThenExtensions, MapExtensions, IsSomeAndExtensions and the rest under Options.Extensions
OptionExtensions
The same set under Results.Extensions
ResultExtensions
Called as extensions, nothing changes. Only a using static or a qualified static call breaks, as CS0234 or CS0103.
Why no warning release. 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 — and [Obsolete] does not remove a member from overload resolution.
The MonadOptionsScope disposal contract
Nothing was removed here; a documented behaviour changed, which is why the release carries a !.
A scope now restores only when it is the innermost one still open. Disposing it at any other point restores nothing and writes a Waystone.Monads.ScopeDisposedOutOfOrder diagnostic event. In 6.x the same mistake restored the wrong options silently.
Why no warning release. There is nothing to mark. The old behaviour was not a member, and a rule cannot see the order in which disposals will happen at run time. The event exists because that is the only place the mistake is visible.
Full contract on Configuration.
Rule ids that are gaps
A retired analyzer rule id is never reused. So a stale .editorconfig entry or #pragma naming one does nothing at all — it does not error, it does not warn, and it reads as though something is configured when nothing is. Delete them.
6.0.0
WM1004, WM1007, WM1009, WM1010, WM2014
7.0.0
WM2010
WM2014 is the one worth knowing about, because it was the migration aid for Option.FlatMap and it went in the same release as the method it pointed at. WM2010 went because there are no implicit conversions left for it to report on.
Removed in 6.0.0
Option.FlatMap
Deprecated in: 5.4.0 · Removed in: 6.0.0 · Replacement: AndThen
Option<T>.FlatMap<TOut>(Func<T, Option<TOut>>)
Option<T>.AndThen<TOut>(Func<T, Option<TOut>>)
FlatMapAsync on Option<T>, Task<Option<T>> and ValueTask<Option<T>>
AndThenAsync
How to migrate
Rename the call. The parameters, the behaviour and the return type do not change.
Upgrade to 5.5.0 first if you have not already. WM2014 reports every call site and its quick fix does the rename. That rule is removed in 6.0.0 along with the method, so it cannot help you after the upgrade.
Why this changed
Result<TOk, TErr> already spelled this operation AndThen, so one library had two names for one idea. Rust, which both types follow, calls it and_then. AndThen is the name that agrees with the rest of the library.
Try overloads that accept an async factory
Deprecated in: 5.2.0 · Removed in: 6.0.0 · Replacement: TryAsync
Option.Try<T>(Func<Task<T>>, …)
Option.TryAsync<T>(Func<Task<T>>, …)
Result.Try<TOk, TErr>(Func<Task<TOk>>, Func<Exception, TErr>, …)
Result.TryAsync<TOk, TErr>(Func<Task<TOk>>, Func<Exception, TErr>, …)
Removing these does not break your build, and that is the problem. Your call site rebinds to the synchronous overload, keeps compiling, and stops catching exceptions. Read Silent change 1 before you upgrade, and turn on WM1011 — it finds every affected call.
How to migrate
Rename the call and keep the await where it already was.
Why this changed
A lambda whose body is a throw expression converts to both Func<T> and Func<Task<T>>. When both overloads are called Try, the compiler cannot choose between them, and you had to declare the delegate type to break the tie:
Giving the async overloads their own name removes the ambiguity, so the call site above compiles as written.
What is not affected
The synchronous overloads keep the Try name:
Option.Try<T>(Func<T>, …)Result.Try<TOk, TErr>(Func<TOk>, Func<Exception, TErr>, …)Result.Try<TOk>(Func<TOk>, …)
Not a deprecation, but it removed API
v6 also closed the Option<T> and Result<TOk, TErr> hierarchies, which removes the ability to derive from them. That was never marked [Obsolete], because there is no way to obsolete "inheriting from this type". See v5.x to v6.x.
See Async for the full async surface, and Upgrading for the upgrades that have already shipped.
For 7.0.0 specifically, Every v7 break lists all of the above plus the changes that were never public API in the first place, each with the compiler diagnostic it produces.
Last updated
Was this helpful?