Resultly 0.7.0
dotnet add package Resultly --version 0.7.0
NuGet\Install-Package Resultly -Version 0.7.0
<PackageReference Include="Resultly" Version="0.7.0" />
<PackageVersion Include="Resultly" Version="0.7.0" />
<PackageReference Include="Resultly" />
paket add Resultly --version 0.7.0
#r "nuget: Resultly, 0.7.0"
#:package Resultly@0.7.0
#addin nuget:?package=Resultly&version=0.7.0
#tool nuget:?package=Resultly&version=0.7.0
Resultly
High-performance functional Result types for .NET โ explicit error handling without exceptions.
Resultly implements Railway Oriented Programming, providing type-safe error handling through discriminated unions. It eliminates the need for try-catch blocks and makes error paths explicit in your type system.
Features
๐ Zero allocation โ struct-based design with aggressive inlining
๐ Type-safe โ closed type hierarchy with exhaustive pattern matching
๐ค๏ธ Railway Oriented Programming โ compose operations that short-circuit on errors
โก Async/await support โ full async variants of all operations
๐ฆ Collection operations โ Traverse, Sequence, Combine for working with multiple Results
๐งฉ Monadic โ Map, Bind, Then, and other functional operators
๐ฏ Validation โ Ensure and EnsureAll for domain validation
๐ Error recovery โ OrElse, Recover, and fallback mechanisms
๐ญ Multiple Error Types โ Result with up to 4 specific error types visible in signature
๐งพ Canonical ResultError โ
Result<TSuccess>facade with structured errors (message/code/metadata)๐ก๏ธ Diagnostics Guards โ detect uninitialized
Result<T>instances with sync/Task/ValueTask helpers๐ท๏ธ Semantic Match โ Source-generated semantic parameter names with sync and async overloads (works for
ResultandTask<Result>across every arity)๐ Type-Based Error Extraction โ
TryGetError<T>()for extracting errors by typeโจ Convenience helpers โ
AnyError/AnyErrorAsyncandTryGetAnyErrorto quickly return or inspect any error from multi-errorResulttypes without writing exhaustiveMatchat call sites๐ Pure Discriminated Unions โ Either types for type-safe unions without success/error semantics, with descriptive
onFirst/onSecondhandlers and optional semantic namesComprehensive โ extensive XML documentation and 300+ tests with high code coverage
โ 4-arity async-fluent MapErrors โ full parity for 4-error
Resulttypes: you can fluently callWithError1Async/WithError2Async/WithError3Async/WithError4Asyncin any order to build mapping pipelines and then callToEitherAsync(); note the mapping order is determined by the final builder shape (the builder arranges mapped slots in the resulting Either in a deterministic order based on which With* was applied).๐งฐ MatchErrors convenience helpers โ thin overloads such as
result.MatchErrors(onError1, onError2)provide a compact way to map a selected subset of error slots directly to anEither<...>without manually composing the full fluent builder chain. Use these for ergonomic, concise error mappings in call sites and examples.
Installation
dotnet add package Resultly
Supports: .NET 9.0 | .NET 8.0 (LTS) | .NET Standard 2.1
Quick Start
Example Project
An end-to-end sample console app lives in Examples/Resultly.ExampleApp. It is part of the solution for easy F5 debugging, yet it is marked as IsPackable=false so it never ships with the NuGet package. The app walks through synchronous/async pipelines, semantic matching, collection helpers, and pure Either unions using the new semantic match helpers. Build or run it directly to exercise the public surface:
dotnet run --project Examples/Resultly.ExampleApp/Resultly.ExampleApp.csproj
If you add new APIs, wiring them into this sample helps the compiler catch integration issues early.
Basic Usage
using Resultly;
// Create Results using implicit conversions
Result<int, string> success = 42;
Result<int, string> failure = "Not found";
// Pattern matching
var message = success.Match(
onSuccess: value => $"Got {value}",
onError: error => $"Error: {error}"
);
Canonical Result<T>
Result<TSuccess> is a thin wrapper over Result<TSuccess, ResultError> that gives you a structured error payload (message, optional code, optional metadata) without having to declare a custom error type.
// Successful result
var quotient = Result<int>.Success(42);
// Structured failure (message + code + metadata)
var failure = Result<int>.Failure(
message: "Division by zero",
code: "DIVIDE_BY_ZERO",
metadata: new Dictionary<string, string?> { ["denominator"] = "0" }
);
// Semantic match works for Result<T>
var message = failure.MatchSemantic(
onSuccess: value => $"Quotient: {value}",
onResultError: error => $"Calculation failed ({error.Code ?? "ERR"}): {error.Message}"
);
// Async overloads are available too
var audit = await failure.MatchSemanticAsync(
async value => { await LogAsync(value); return "ok"; },
async error => { await LogErrorAsync(error); return "fail"; }
);
// Metadata helpers let you update context without rebuilding dictionaries
var traceId = Guid.NewGuid().ToString("N");
var correlationId = traceId.ToUpperInvariant();
var enriched = failure.AddMetadata("operation", "Divide");
var replaced = failure
.AddMetadataRange(new Dictionary<string, string?> { ["traceId"] = traceId })
.RemoveMetadata("operation")
.WithMetadata(
new Dictionary<string, string?> { ["correlationId"] = correlationId },
StringComparer.OrdinalIgnoreCase
);
// Default(Result<T>) surfaces ResultError.Uninitialized for easy guard clauses
if (!default(Result<int>).IsInitialized())
{
Console.WriteLine("Result<int> was not initialized before use.");
}
// ThrowIfUninitialized() provides a straightforward guard for API entry points
var result = default(Result<int>);
result.ThrowIfUninitialized(); // throws InvalidOperationException with a descriptive message
// ValueTask overloads are available when the result is produced asynchronously
var pending = new ValueTask<Result<int>>(Result<int>.Success(5));
await pending.ThrowIfUninitialized();
Multiple Specific Error Types
Define Results with multiple explicit error types:
public record User(string Name, string Email);
public record NotFoundError(string Message);
public record DatabaseError(string Message);
public record ValidationError(string Message);
// All error types visible in the signature
public Result<User, NotFoundError, DatabaseError> GetUser(int id)
{
if (id < 0)
return new ValidationError("Invalid ID"); // Won't compile - not in signature!
if (!_db.Exists(id))
return new NotFoundError($"User {id} not found");
try
{
return _db.GetUser(id);
}
catch
{
return new DatabaseError("Connection failed");
}
}
// Use semantic Match - generated automatically!
var result = GetUser(42);
result.MatchSemantic(
onSuccess: user => Console.WriteLine($"Hello, {user.Name}"),
onNotFoundError: err => Console.WriteLine($"404: {err.Message}"),
onDatabaseError: err => Console.WriteLine($"DB Error: {err.Message}")
);
// Async version preserves semantic naming across awaits
var greeting = await result.MatchSemanticAsync(
async user =>
{
await emailService.SendWelcome(user);
return $"Sent email to {user.Email}";
},
async notFound =>
{
await telemetry.TrackMiss(notFound.Message);
return $"Missing {notFound.Message}";
},
async dbError =>
{
await telemetry.TrackFailure(dbError.Message);
return $"Database issue {dbError.Message}";
}
);
// Or extract errors by type
if (result.TryGetError<DatabaseError>(out var dbError))
{
_logger.LogError("Database issue: {Message}", dbError.Message);
// Retry logic here
}
Benefits:
- Error types are visible in the method signature
- IntelliSense shows all possible error types
- Semantic Match parameter names (generated automatically via source generator)
- Type-safe error extraction with
TryGetError<T>() - Compiler enforces handling of all error cases
Either Types (Pure Discriminated Unions)
Sometimes you need type-safe unions without success/error semantics. Either types provide pure discriminated unions where all cases are semantically equal:
using Resultly;
// Either2: A value that is one of two types
public record UserInput();
public record AdminInput();
public Either<UserInput, AdminInput> ParseInput(string role, string data)
{
return role == "admin"
? Either<UserInput, AdminInput>.Second(new AdminInput())
: Either<UserInput, AdminInput>.First(new UserInput());
}
// Pattern match all cases
var result = ParseInput("user", "data");
result.Match(
onFirst: userInput => ProcessUserInput(userInput),
onSecond: adminInput => ProcessAdminInput(adminInput)
);
// Or use MatchSemantic to stick to type-based names
result.MatchSemantic(
onUserInput: user => ProcessUserInput(user),
onAdminInput: admin => ProcessAdminInput(admin)
);
Either3/4/5 for Multiple Types:
// Payment methods
public record CreditCard(string Number);
public record PayPal(string Email);
public record BankTransfer(string IBAN);
public record Cryptocurrency(string Wallet);
public Either<CreditCard, PayPal, BankTransfer, Cryptocurrency> GetPaymentMethod()
{
// Return any of the four types
return Either<CreditCard, PayPal, BankTransfer, Cryptocurrency>
.Third(new BankTransfer("DE89370400440532013000"));
}
// Exhaustive matching required
paymentMethod.Match(
onFirst: cc => ProcessCreditCard(cc),
onSecond: pp => ProcessPayPal(pp),
onThird: bt => ProcessBankTransfer(bt),
onFourth: crypto => ProcessCrypto(crypto)
);
Type Extraction:
// Try to extract a specific type
if (paymentMethod.TryPick<PayPal>(out var paypal))
{
Console.WriteLine($"PayPal: {paypal.Email}");
}
// Or get all values as tuples
var (card, paypal, bank, crypto) = paymentMethod.Get();
// Only one will be non-null
Practical Use Cases:
// 1. Configuration sources
public Either<JsonConfig, XmlConfig, YamlConfig> LoadConfig(string path)
{
var ext = Path.GetExtension(path);
return ext switch
{
".json" => Either<JsonConfig, XmlConfig, YamlConfig>.First(ParseJson(path)),
".xml" => Either<JsonConfig, XmlConfig, YamlConfig>.Second(ParseXml(path)),
".yaml" => Either<JsonConfig, XmlConfig, YamlConfig>.Third(ParseYaml(path)),
_ => throw new NotSupportedException()
};
}
// 2. Protocol messages
public record HttpRequest();
public record WebSocketMessage();
public record GrpcCall();
public Either<HttpRequest, WebSocketMessage, GrpcCall> ReceiveMessage()
{
// Return the appropriate message type
}
// 3. State machines
public record Idle();
public record Processing();
public record Completed();
public record Failed();
public Either<Idle, Processing, Completed, Failed> GetCurrentState()
{
// Return current state without success/error connotation
}
Key Differences from Result:
| Feature | Result | Either |
|---|---|---|
| Semantics | Success vs. Error | Equal alternatives |
| Short-circuiting | Operators stop on Error | No special behavior |
| Use case | Error handling | Type unions, state machines |
| Pattern matching | onSuccess/onError | onFirst/onSecond/onThird... |
| Operators | Map, Bind, Then, etc. | Match, TryPick, Get |
When to use Either vs Result:
- Use Result when modeling operations that can fail (database queries, validations, IO)
- Use Either when modeling a choice between semantically equal alternatives (config formats, message types, state machines)
Railway Oriented Programming
Chain operations that automatically short-circuit on errors:
var result = GetUserEmail(userId)
.Bind(ValidateEmail)
.Map(email => email.ToLower())
.Bind(SendNotification);
LINQ Query Syntax
var result =
from user in GetUser(id)
from profile in GetProfile(user.ProfileId)
where profile.IsActive
select new UserViewModel(user, profile);
Core Concepts
Chain operations that automatically short-circuit on the first error:
var result = ValidateInput(input)
.Then(ParseValue)
.Ensure(x => x > 0, _ => "Must be positive")
.Map(x => x * 2)
.Then(SaveToDatabase)
.MapError(error => $"Pipeline failed: {error}");
// Handle the result
result.Match(
onSuccess: id => Console.WriteLine($"Saved with ID: {id}"),
onError: error => Console.WriteLine($"Error: {error}")
);
Core Operations
Construction
// Implicit conversions
Result<int, string> success = 42;
Result<int, string> failure = "error";
// Static factory methods
Result<int, string>.Success(42)
Result<int, string>.Failure("error")
// Lift regular values
Result<int, string>.FromValue(42)
Map
Transform success values while preserving errors:
Result<int, string> result = 42;
var doubled = result.Map(x => x * 2); // Ok(84)
// Async variant
var asyncResult = await result.MapAsync(async x => await ComputeAsync(x));
Bind (FlatMap)
Chain Result-returning functions without nesting:
Result<string, string> GetUserId(string email) { /* ... */ }
Result<User, string> LoadUser(string userId) { /* ... */ }
var user = GetUserId("user@example.com")
.Bind(LoadUser);
// Async variant
var asyncUser = await GetUserIdAsync(email)
.BindAsync(async id => await LoadUserAsync(id));
Then
Railway-style continuation (alias for Bind with clearer intent):
var result = ParseInput(input)
.Then(Validate)
.Then(Transform)
.Then(Persist);
// Async variant
var asyncResult = await ParseInputAsync(input)
.ThenAsync(ValidateAsync)
.ThenAsync(TransformAsync);
Ensure & EnsureAll
Validate and convert to error if predicate fails:
var result = Result<int, string>.Success(150)
.Ensure(x => x >= 0, _ => "Must be non-negative")
.Ensure(x => x <= 100, x => $"{x} exceeds maximum");
// EnsureAll: Apply multiple validations, collect all errors
var validated = result.EnsureAll(
(x => x >= 0, _ => "Must be non-negative"),
(x => x <= 100, _ => "Must not exceed 100"),
(x => x % 2 == 0, _ => "Must be even")
);
Match
Exhaustively handle both cases:
// Match with return value
var output = result.Match(
onSuccess: value => ProcessValue(value),
onError: error => HandleError(error)
);
// Match with side effects (void)
result.Match(
onSuccess: value => Console.WriteLine(value),
onError: error => Console.Error.WriteLine(error)
);
// MatchSemantic for Result3/4/5 (source-generated)
result.MatchSemantic(
onSuccess: user => HandleSuccess(user),
onNotFoundError: err => HandleNotFound(err),
onDatabaseError: err => HandleDatabase(err)
);
// Async variant (Result or Task<Result>)
var summary = await result.MatchSemanticAsync(
async user => await BuildSummary(user),
async notFound => await HandleNotFoundAsync(notFound),
async dbError => await HandleDatabaseAsync(dbError)
);
Type Extraction
// Extract specific error type from Result with multiple errors
if (result.TryGetError<DatabaseError>(out var dbError))
{
_logger.LogError("Database issue: {Message}", dbError.Message);
}
// For Either types
if (either.TryPick<PayPal>(out var paypal))
{
ProcessPayPal(paypal);
}
// Get all values as tuple (only one non-null)
var (first, second, third) = either.Get();
Async Support
All operations have async variants:
var result = await ValidateAsync(input)
.ThenAsync(async x => await SaveAsync(x))
.MapAsync(async x => await TransformAsync(x));
Complete Operator Reference
Transformation Operators
- Map / MapAsync - Transform success value
- MapError - Transform error value
- Bind / BindAsync - Chain Result-returning operations (flatMap)
- Then / ThenAsync - Alias for Bind with clearer semantic intent
Validation Operators
- Ensure - Single validation with predicate
- EnsureAll - Multiple validations, collect all errors
- Where - LINQ filter support
Error Recovery Operators
- OrElse / OrElseAsync - Provide fallback on error
- Recover - Convert error to success value
- DefaultWith - Provide default value on error
- DefaultIfError - Use default value from function
Side Effect Operators
- Do - Execute action on success without transformation
- DoWhenError - Execute action on error without transformation
- DoAsync / DoWhenErrorAsync - Async variants
Inspection Operators
- Match / MatchAsync - Exhaustive pattern matching with return value
- MatchSemantic - Type-specific semantic matching (generated)
- TryGetError<T> - Extract specific error type
- TryPick<T> - Extract specific type from Either
- Get - Get all values as tuple (Either types)
Query Operators (LINQ)
- Select / SelectMany - LINQ query syntax support
- Where - Filter with predicate
Conversion Operators
- ToEither - Convert Result to Either
- ToOption - Convert to Option/Maybe type (if implemented)
Note: the canonical conversion APIs are ToEither and the MapErrors() builder + ToEither() terminal. A number of small convenience wrappers (for example MapToEither / MapSuccessToEither and a few thin MapToEitherE1E2/MapToEitherE2E3 helpers) were removed to keep the API surface focused. Use one of the following migration patterns:
- Convert a single error slot:
result.ToEither(onError1)orawait result.ToEitherAsync(onError1Async) - Convert two slots into a two-case Either:
result.ToEitherMap(OnFirstError: e1 => left, OnSecondError: e2 => right) - Use the fluent builder to map selected slots:
result.MapErrors().WithError1(e1 => map1).WithError2(e2 => map2).ToEither()
For ergonomic fluent chains the lightweight top-level forwarding helpers WithError1/WithError2/WithError3/WithSuccess still forward to MapErrors() so existing short-call call sites like result.WithError1(...).WithError3(...).ToEither() continue to work.
Type Hierarchy
Result Types
// Result<TSuccess, TError> - Single error type
Result<User, string> user = GetUser(id);
// Result<TSuccess, TError1, TError2> - Two error types
Result<User, NotFoundError, DatabaseError> user = GetUser(id);
// Result<TSuccess, TError1, TError2, TError3> - Three error types
Result<User, NotFoundError, DatabaseError, ValidationError> user = GetUser(id);
// Result<TSuccess, TError1, TError2, TError3, TError4> - Four error types
Result<User, NotFoundError, DatabaseError, ValidationError, TimeoutError> user = GetUser(id);
Either Types
// Either<T1, T2> - Two alternatives
Either<JsonConfig, XmlConfig> config = LoadConfig();
// Either<T1, T2, T3> - Three alternatives
Either<JsonConfig, XmlConfig, YamlConfig> config = LoadConfig();
// Either<T1, T2, T3, T4> - Four alternatives
Either<CreditCard, PayPal, BankTransfer, Cryptocurrency> payment = GetPayment();
// Either<T1, T2, T3, T4, T5> - Five alternatives
Either<Idle, Processing, Completed, Failed, Cancelled> state = GetState();
Construction Examples
// Result construction
var success = Result<int, string>.Success(42);
var failure = Result<int, string>.Failure("error");
// Implicit conversion
Result<int, string> r1 = 42; // Success
Result<int, string> r2 = "error"; // Failure
// Multi-error Result construction
var notFound = Result<User, NotFoundError, DatabaseError>.Error1(new NotFoundError());
var dbError = Result<User, NotFoundError, DatabaseError>.Error2(new DatabaseError());
// Either construction
var first = Either<string, int>.First("hello");
var second = Either<string, int>.Second(42);
// Multi-type Either
var json = Either<JsonConfig, XmlConfig, YamlConfig>.First(config);
var xml = Either<JsonConfig, XmlConfig, YamlConfig>.Second(config);
var yaml = Either<JsonConfig, XmlConfig, YamlConfig>.Third(config);
Collection Operations
Sequence
Combine multiple Results, short-circuiting on first error:
var results = new[]
{
ParseInt("1"),
ParseInt("2"),
ParseInt("3")
};
Result<IReadOnlyList<int>, string> combined = results.Sequence();
// Ok([1, 2, 3])
// If any fails, returns first error
var withError = new[] { ParseInt("1"), ParseInt("bad"), ParseInt("3") };
var failed = withError.Sequence(); // Error("Invalid number")
Traverse
Apply a Result-returning function to each element:
var numbers = new[] { "1", "2", "3" };
var result = numbers.Traverse(ParseInt);
// Ok([1, 2, 3])
// Short-circuits on first error
var mixed = new[] { "1", "bad", "3" };
var failed = mixed.Traverse(ParseInt); // Error on "bad"
Combine
Collect all errors instead of short-circuiting:
var validations = new[]
{
ValidateEmail(email),
ValidateAge(age),
ValidatePassword(password)
};
var results = validations.Combine();
// Returns all validation errors if any fail
// Success only if all succeed
Async Collection Operations
// SequenceAsync
var asyncResults = new[] { GetUserAsync(1), GetUserAsync(2) };
var combined = await asyncResults.SequenceAsync();
// TraverseAsync
var ids = new[] { 1, 2, 3 };
var users = await ids.TraverseAsync(async id => await GetUserAsync(id));
Error Handling & Recovery
MapError
Transform error types:
var result = operation()
.MapError(ex => new ValidationError(ex.Message));
// Change error type
Result<int, DatabaseError> dbResult = GetFromDatabase();
Result<int, string> stringResult = dbResult.MapError(e => e.Message);
OrElse
Provide alternative on error:
var result = TryPrimarySource()
.OrElse(error => TrySecondarySource())
.OrElse(error => TryTertiarySource());
// Async variant
var asyncResult = await TryPrimaryAsync()
.OrElseAsync(async err => await TrySecondaryAsync());
Recover
Convert errors to success values:
var result = RiskyOperation()
.Recover(error => DefaultValue);
// Conditional recovery
var recovered = operation()
.Recover(error => error switch
{
NotFoundError => DefaultUser,
TimeoutError => CachedUser,
_ => throw new InvalidOperationException()
});
Do & DoWhenError
Execute side effects without transforming the Result:
var result = operation()
.Do(value => _logger.LogInformation("Success: {Value}", value))
.DoWhenError(error => _logger.LogError("Failed: {Error}", error));
// Continues the pipeline
result.Do(LogSuccess).DoWhenError(LogError).Map(Transform);
Practical Example
public record User(int Id, string Email, int Age);
public record ValidationError(string Field, string Message);
public Result<User, ValidationError> CreateUser(string email, int age)
{
return ValidateEmail(email)
.Bind(validEmail => ValidateAge(age)
.Map(validAge => new User(0, validEmail, validAge)))
.Then(SaveUser);
}
Result<string, ValidationError> ValidateEmail(string email)
{
return email.Contains("@")
? Result<string, ValidationError>.Success(email)
: Result<string, ValidationError>.Failure(
new ValidationError("Email", "Invalid format"));
}
Result<int, ValidationError> ValidateAge(int age)
{
return age >= 18
? Result<int, ValidationError>.Success(age)
: Result<int, ValidationError>.Failure(
new ValidationError("Age", "Must be 18 or older"));
}
Result<User, ValidationError> SaveUser(User user)
{
// Database operation
return Result<User, ValidationError>.Success(user with { Id = 123 });
}
// Usage
var result = CreateUser("user@example.com", 25);
result.Match(
onSuccess: user => Console.WriteLine($"Created user {user.Id}"),
onError: error => Console.WriteLine($"{error.Field}: {error.Message}")
);
ASP.NET Core Integration
[HttpPost]
public IActionResult CreateUser([FromBody] CreateUserRequest request)
{
var result = ValidateRequest(request)
.Then(CreateUserFromRequest)
.Then(SaveToDatabase);
return result.Match(
onSuccess: user => Ok(new { userId = user.Id }),
onError: error => BadRequest(new { error = error.Message })
);
}
Philosophy
Resultly embraces these principles:
- Make invalid states unrepresentable โ errors are explicit in the type system
- Fail fast and explicitly โ no hidden exceptions, all failures are values
- Compose operations โ build complex logic from simple, composable parts
- Railway Oriented Programming โ success and failure paths are separate tracks
Performance
Resultly is designed for zero-allocation scenarios:
- Struct-based types where possible
- Aggressive inlining via
[MethodImpl] - No reflection or dynamic dispatch
- Minimal boxing/unboxing
Comparison with Exceptions
Traditional exception handling:
try
{
var user = GetUser(id);
var validated = ValidateUser(user);
var saved = SaveUser(validated);
return saved;
}
catch (NotFoundException ex)
{
// Handle not found
}
catch (ValidationException ex)
{
// Handle validation
}
catch (DatabaseException ex)
{
// Handle database error
}
With Resultly:
return GetUser(id)
.Then(ValidateUser)
.Then(SaveUser)
.MapError(error => /* handle all errors uniformly */);
License
MIT License - see LICENSE for details.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Formatting Hook
Run scripts/install-format-hook.ps1 once to wire up a pre-commit hook that executes dotnet msbuild -target:FormatAll. The hook blocks the commit if formatting fails or produces additional changes, making it easier to keep the repo consistent:
pwsh scripts/install-format-hook.ps1
Credits
Inspired by:
- F# Result type
- Rust's Result<T, E>
- Railway Oriented Programming by Scott Wlaschin
- Functional programming patterns in Haskell and Scala
Semantic Match Extensions (New Package)
Note: As of November 2025, semantic match extension methods are distributed in a separate package:
Resultlyโ core types only (no generator, no semantic match extensions)Resultly.Extensions.SemanticMatchโ add this package to get semantic match extensions for all Resultly types (includes the generator)To use semantic match extensions, reference only
Resultly.Extensions.SemanticMatchin your project:dotnet add package Resultly.Extensions.SemanticMatchThis package depends on
Resultlyand includes the source generator. Do not referenceResultly.Generatorsdirectly.This structure avoids ambiguous extension methods and ensures only one set of semantic match extensions is generated per build.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.1 is compatible. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.1
- System.Collections.Immutable (>= 8.0.0)
-
net8.0
- System.Collections.Immutable (>= 8.0.0)
-
net9.0
- System.Collections.Immutable (>= 8.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.7.0 | 213 | 11/13/2025 |
| 0.5.0 | 212 | 11/13/2025 |
| 0.4.0 | 214 | 11/13/2025 |
| 0.3.0 | 215 | 11/13/2025 |
| 0.2.4 | 251 | 11/11/2025 |
| 0.2.3 | 182 | 11/9/2025 |
| 0.2.0-alpha.8 | 144 | 11/9/2025 |
| 0.2.0-alpha.7 | 147 | 11/9/2025 |
| 0.2.0-alpha.6 | 142 | 11/9/2025 |
| 0.2.0-alpha.5 | 141 | 11/3/2025 |
| 0.2.0-alpha.4 | 129 | 11/3/2025 |
| 0.2.0-alpha.2 | 130 | 11/2/2025 |
| 0.2.0-alpha.1 | 138 | 11/2/2025 |
| 0.1.0-alpha.3 | 70 | 11/2/2025 |
| 0.1.0-alpha.1 | 70 | 11/2/2025 |