For the complete documentation index, see llms.txt. This page is also available as Markdown.

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.

Packages in the Waystone family share one version number, so a v6.0.0 of Waystone.Monads means a v6.0.0 of every package.

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

Removed
Replacement

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.

You may not need to log at all. If you only wanted counts, the library publishes a Waystone.Monads meter and needs no package and no configuration — see Observability.

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]

Removed
Replacement

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))

Error.FromEnum and Result.Err<TOk>(Enum, string) were deprecated in 6.3.0, a release after the other two. They call the same reflection path and disappear at the same time, but they carried no warning until now.

How to migrate

Mark the enum with [ErrorCodeCatalog] and use what it generates.

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

Removed
Replacement

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

Removed
Replacement

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.

Retired in
Ids

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

Removed
Replacement

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

Removed
Replacement

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>, …)

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>, …)

Result.TryAsync<TOk>(Func<Task<TOk>>, …) , the overload that defaults the error type to Error, was introduced as TryAsync and never had a Try spelling. There was nothing to migrate.

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?