OneOf: Compile-Time Exhaustive Matching for C# Method Return Types
Easy to use F#-like ~discriminated~ unions for C# with exhaustive compile time matching
At a glance
- What is it?
- OneOf is a C# library that provides F#-style discriminated unions using a generic type OneOf<T0,...Tn>. Its main purpose is to make method signatures express every possible return type explicitly, with the compiler enforcing that all cases are handled before the code will build.
- Who is it for?
- OneOf fits C# developers who write methods that can return structurally different outcomes, such as a success value, a validation error, or a conflict, and who want the compiler to enforce that every caller handles every case. It is unnecessary overhead when a method only ever succeeds or throws.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 111 days ago.
- What is it written in?
- Mainly C#, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 25, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The Problem OneOf Addresses
C# methods that can fail in multiple ways typically communicate that through exceptions, nullable return types, or custom base-class hierarchies. Each approach has a known drawback: exceptions used for control flow are a recognized antipattern, nullable types lose type information about what the absent case means, and custom hierarchies require the caller to know about all subtypes without the compiler checking that they handled them all.
OneOf addresses this by encoding multiple possible return types in the method signature itself. The library's README describes discriminated unions as a natural alternative to polymorphism when you need guaranteed behavior per type, citing F# and Scala as languages where this pattern is idiomatic. For C# developers who cannot or do not want to switch languages, OneOf brings the same guarantee without leaving the ecosystem.
Expressing Results with OneOf<T0,...Tn>
The core type is OneOf<T0, T1, ... Tn>, which holds exactly one value of one of the listed types. A method using this as its return type communicates all possible outcomes in its signature:
public OneOf<User, InvalidName, NameTaken> CreateUser(string username)
{
if (!IsValid(username)) return new InvalidName();
var user = _repo.FindByUsername(username);
if(user != null) return new NameTaken();
var user = new User(username);
_repo.Save(user);
return user;
}Each return value is implicitly converted to the OneOf type. The caller cannot ignore the result: to get a value out of a OneOf, they must handle all type cases. The README notes that adding a new generic parameter to the return type forces every call site to be updated, since the compiler will refuse to compile code that omits the new case handler. In large codebases, this is the primary argument for the pattern.
OneOf also comes with a set of ready-made types in the OneOf.Types namespace: Yes, No, Maybe, Unknown, True, False, All, Some, and None, useful for simple binary or optional-style unions without defining new classes.
The Match and Switch APIs
The Match method turns a OneOf into a value by applying one function per possible type. The number of lambda arguments must match the number of type parameters:
OneOf<string, ColorName, Color> backgroundColor = ...;
Color c = backgroundColor.Match(
str => CssHelper.GetColorFromString(str),
name => new Color(name),
col => col
);
_window.BackgroundColor = c;If a future change adds a fourth type parameter to backgroundColor, the code above will not compile until a fourth handler is added. This is the exhaustiveness guarantee: the compiler makes adding a case a breaking change in the best sense.
The Switch method works the same way but for side effects rather than return values, accepting Action delegates instead of Func delegates. There is also a .Switch method for when you aren't returning a value, as the README states.
TryPickX for Conditional Branching
When exhaustive matching is not required and only one type case is relevant, the TryPickX methods offer an alternative. For a OneOf<T0, T1, T2>, the library provides:
public bool TryPickT0(out T0 value, out OneOf<T1, T2> remainder) { ... }
public bool TryPickT1(out T1 value, out OneOf<T0, T2> remainder) { ... }
public bool TryPickT2(out T2 value, out OneOf<T0, T1> remainder) { ... }The method returns true if the OneOf holds the T𝑥 type, setting value to the unwrapped result. If it holds a different type, it returns false and sets remainder to a narrower OneOf of the remaining types. This lets callers peel off one type at a time through a series of if checks rather than using Match with three lambdas.
Named Union Types with OneOfBase and Source Generation
When the same combination of types recurs across a codebase, defining a named type reduces verbosity. Inheriting from OneOfBase<T0,...Tn> creates a named alias that still inherits Match, Switch, and TryPickX:
[GenerateOneOf]
public partial class StringOrNumber : OneOfBase<string, int> { }The [GenerateOneOf] attribute triggers a Roslyn source generator from the OneOf.SourceGenerator package, which writes the boilerplate constructor and implicit conversion operators at compile time. Without the source generator, those members must be written manually. The source generator is distributed as a separate NuGet package named OneOf.SourceGenerator.
Installing the base library uses a single line in the NuGet console:
install-package OneOfThe OneOf.Extended package adds further utility types. Both the library and the source generator follow the MIT license.
Where OneOf Is the Wrong Tool
OneOf adds type-level complexity to every method signature that uses it. A method that returns OneOf<T0, T1, T2> requires callers to always handle three types even when in practice one path dominates. Teams unfamiliar with the discriminated union pattern may find the lambda-based Match call harder to read than a conventional switch statement.
The library also does not address asynchronous patterns directly. Using OneOf with Task<OneOf<T0, T1>> adds a layer of type nesting that requires careful handling in async/await chains.
The repository has no tagged GitHub releases. The NuGet package has a version number, but tracking library changes requires monitoring NuGet directly. The README does not document a migration guide or a breaking-change policy.
F# Discriminated Unions: The Same Idea in a Different Language
F# has discriminated unions as a first-class language feature. A union type in F# is declared with the type keyword and cases are matched with the match expression, with the compiler enforcing exhaustiveness by default. The guarantee is identical to what OneOf provides for C#, but F# enforces it at the syntax level rather than through a generic library.
The trade-off is the language itself. F# discriminated unions require writing F# code, which may not be possible in a project fully committed to C#. OneOf provides the same structural guarantee without leaving C#, at the cost of verbose generic type parameters and a library dependency. The README is direct about this: it describes the library as bringing F# and Scala-style discriminated unions to C# for developers who want the pattern without switching languages.
Maintenance Status and License
The last push to the repository was on 2026-06-11. The repository is not archived. The library and its components are licensed under the MIT License. The OneOf.SourceGenerator and OneOf.Extended packages are part of the same repository under the Generator/ and OneOf.Extended/ subdirectories respectively, which means they track together rather than being separately versioned.
Editorial conclusion
OneOf fits C# developers who write methods that can return structurally different outcomes, such as a success value, a validation error, or a conflict, and who want the compiler to enforce that every caller handles every case. It is unnecessary overhead when a method only ever succeeds or throws. Before adopting it, verify that your team is comfortable with the generic syntax, since OneOf<T0, T1, T2> on every method signature can be verbose, and note that the repository has no tagged GitHub releases to pin.
Frequently asked questions
What is OneOf in C# and when should you use it?
OneOf is a C# library that provides discriminated union types using the generic class OneOf<T0,...Tn>. Use it when a method can return structurally different outcomes and you want the compiler to enforce that every caller handles every case, as an alternative to exceptions or nullable return types.
How does OneOf enforce exhaustive matching at compile time?
The Match method requires one lambda per type parameter. If you add a new type parameter to a OneOf return type, every call site that uses Match will fail to compile until a handler for the new type is provided. The README describes this as the key advantage over switch statements, which can have a default fallback that silently ignores new cases.
How do you install OneOf?
Run install-package OneOf in the NuGet Package Manager Console. To use source generation for named union types, also install install-package OneOf.SourceGenerator as a separate package.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/mcintyre321-oneof)