> For the complete documentation index, see [llms.txt](https://draekien-industries.wpei.me/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://draekien-industries.wpei.me/upgrading/deprecations.md).

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

{% hint style="info" %}
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.
{% endhint %}

**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
```

```diff
-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:

```diff
-MonadOptions.Configure(options => options.UseExceptionLogger(MyLogger.Handle));
+MonadOptions.Configure(options => options.UseLoggerFactory(loggerFactory));
```

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](/guides/observability.md).

{% hint style="danger" %}
**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.
{% endhint %}

{% hint style="info" %}
**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](/guides/observability.md).
{% endhint %}

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

{% hint style="info" %}
`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.
{% endhint %}

#### How to migrate

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

{% hint style="warning" %}
**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.
{% endhint %}

```diff
-ErrorCode code = ErrorCode.FromEnum(OrderError.NotFound);
+ErrorCode code = OrderErrorCatalog.Codes.NotFound;
```

```diff
-Error error = Error.FromEnum(OrderError.NotFound, "no order with that id");
+Error error = OrderErrorCatalog.Errors.NotFound("no order with that id");
```

```diff
-Result<int, Error> result = Result.Err<int>(OrderError.NotFound, "gone");
+Result<int, Error> result = Result.Err<int>(OrderErrorCatalog.Errors.NotFound("gone"));
```

When the member is not known where you are standing, the generated extensions take their place, and the fix produces these instead:

```diff
-ErrorCode code = ErrorCode.FromEnum(error);
+ErrorCode code = error.ToErrorCode();
```

```diff
-Error error = Error.FromEnum(value, "gone");
+Error error = value.ToError("gone");
```

If you shaped your codes by subclassing `ErrorCodeFactory` and overriding `FromEnum`, say the same thing with a `Format` — see [Source generation](/reference/source-generation.md). `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](/reference/source-generation/reviewing-codes.md#reviewing-your-codes-as-a-list) 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](/guides/configuration.md#what-happens-when-you-dispose-out-of-order).

### 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`](#optionflatmap) 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.

```diff
-Option<int> option = Find(id).FlatMap(Parse);
+Option<int> option = Find(id).AndThen(Parse);
```

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

{% hint style="danger" %}
**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](/upgrading/older/v5-to-v6.md#silent-change-1-try-with-an-async-factory) before you upgrade, and turn on [`WM1011`](/reference/analyzers/runtime-bugs.md#wm1011) — it finds every affected call.
{% endhint %}

#### How to migrate

Rename the call and keep the `await` where it already was.

```diff
-Option<int> option = await Option.Try(() => FetchAsync());
+Option<int> option = await Option.TryAsync(() => FetchAsync());

-Result<int, string> result = await Result.Try(() => FetchAsync(), ex => ex.Message);
+Result<int, string> result = await Result.TryAsync(() => FetchAsync(), ex => ex.Message);
```

#### 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:

```csharp
// ambiguous, will not compile
Result<int, Error> result = Result.Try<int>(() => throw new InvalidOperationException());

// what you had to write instead
Result<int, Error> result = Result.Try<int>(new Func<int>(() => throw new InvalidOperationException()));
```

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

{% hint style="info" %}
`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.
{% endhint %}

## 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](/upgrading/older/v5-to-v6.md#loud-change-you-can-no-longer-derive-from-option-or-result).

See [Async](/guides/async.md) for the full async surface, and [Upgrading](/upgrading/upgrading.md) for the upgrades that have already shipped.

For 7.0.0 specifically, [Every v7 break](/upgrading/v7/breaking-changes.md) lists all of the above plus the changes that were never public API in the first place, each with the compiler diagnostic it produces.
