Ti-Soft.Results 2.0.0

dotnet add package Ti-Soft.Results --version 2.0.0
                    
NuGet\Install-Package Ti-Soft.Results -Version 2.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Ti-Soft.Results" Version="2.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Ti-Soft.Results" Version="2.0.0" />
                    
Directory.Packages.props
<PackageReference Include="Ti-Soft.Results" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Ti-Soft.Results --version 2.0.0
                    
#r "nuget: Ti-Soft.Results, 2.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Ti-Soft.Results@2.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Ti-Soft.Results&version=2.0.0
                    
Install as a Cake Addin
#tool nuget:?package=Ti-Soft.Results&version=2.0.0
                    
Install as a Cake Tool

Results

Лёгкая библиотека для представления успешного и неуспешного результата без использования исключений в ожидаемых сценариях.

Библиотека предоставляет типы Result, Result<T>, Result<T, E> и UnitResult<E>, а также фабрики Success/Failure, методы Try, Combine и compositional API (Match, Map, Bind, Ensure, Tap, TapError, MapError) в синхронном и асинхронном вариантах.

Для чего нужна библиотека

Results позволяет:

  • явно выражать успех/неуспех операции;
  • избегать исключений там, где ошибка является ожидаемой частью доменной логики;
  • строить линейные pipeline-цепочки без множества if и ручного проброса ошибок;
  • удобно работать как со строковыми ошибками, так и с типизированными ошибками.

Поддерживаемые платформы

Библиотека мультитаргетится на:

  • net6.0
  • net7.0
  • net8.0
  • net9.0
  • net10.0

Хотя .NET 6 и .NET 7 уже вышли из официальной поддержки Microsoft, они намеренно сохранены в библиотеке ради совместимости с существующими проектами.

Основные типы

Result

Неуспех/успех без значения, с ошибкой типа string.

using ResultType;

Result result = Result.Success();
Result failed = Result.Failure("Something went wrong");

Result<T>

Неуспех/успех со значением, с ошибкой типа string.

Result<int> success = Result.Success(42);
Result<int> failure = Result.Failure<int>("Value was not produced");

Result<T, E>

Неуспех/успех со значением и типизированной ошибкой.

Result<int, string> success = Result.Success<int, string>(42);
Result<int, string> failure = Result.Failure<int, string>("Domain error");

UnitResult<E>

Неуспех/успех без значения, но с типизированной ошибкой.

UnitResult<string> success = UnitResult.Success<string>();
UnitResult<string> failure = UnitResult.Failure("Domain error");

Базовые правила

У каждого результата есть два свойства состояния:

  • IsSuccess
  • IsFailure

Они взаимоисключающие.

Доступ к Value и Error

В библиотеке намеренно защищаются инварианты объекта:

  • Value доступно только для успешного результата;
  • Error доступно только для неуспешного результата.

Нарушение этих правил приводит к специализированным исключениям библиотеки.

Это сделано специально: ошибка доступа к неправильной ветке результата считается ошибкой использования API, а не нормальным рабочим сценарием.

Фабрики Success / Failure

Result ok = Result.Success();
Result fail = Result.Failure("error");

Result<string> okValue = Result.Success("hello");
Result<string> failValue = Result.Failure<string>("error");

Result<int, string> typedOk = Result.Success<int, string>(10);
Result<int, string> typedFail = Result.Failure<int, string>("typed error");

UnitResult<string> unitOk = UnitResult.Success<string>();
UnitResult<string> unitFail = UnitResult.Failure("unit error");

Try

Try позволяет обернуть код, который может выбросить исключение, в Result.

Без возвращаемого значения

Result result = Result.Try(() =>
{
    DoWork();
});

С возвращаемым значением

Result<int> result = Result.Try(() =>
{
    return int.Parse("42");
});

С типизированной ошибкой

Result<int, string> result = Result.Try(
    () => int.Parse("42"),
    ex => $"Parsing failed: {ex.Message}");

Для Action / Task с типизированной ошибкой

UnitResult<string> result = Result.Try(
    () => DoWork(),
    ex => $"Action failed: {ex.Message}");
UnitResult<string> result = await Result.Try(
    async () => await DoWorkAsync(),
    ex => $"Async action failed: {ex.Message}");

Combine

Combine нужен, когда есть несколько независимых результатов, и нужно:

  • вернуть успех, если успешны все;
  • собрать ошибки, если есть хотя бы один failure.

Нетипизированный вариант

var results = new[]
{
    Result.Success(),
    Result.Failure("Name is empty"),
    Result.Failure("Email is invalid")
};

Result combined = Result.Combine(results);

Result<T>

var results = new[]
{
    Result.Success(1),
    Result.Success(2),
    Result.Success(3)
};

Result<int[]> combined = Result.Combine(results);

Result<T, E>

var results = new[]
{
    Result.Success<int, string>(1),
    Result.Failure<int, string>("Invalid item")
};

Result<int[], string[]> combined = Result.Combine(results);

UnitResult<E>

var results = new[]
{
    UnitResult.Success<string>(),
    UnitResult.Failure("Validation failed")
};

UnitResult<string[]> combined = Result.Combine(results);

Composition API

Match

Используется как финальный выход из мира Result.

string text = result.Match(
    onSuccess: value => $"OK: {value}",
    onFailure: error => $"ERROR: {error}");

Map

Преобразует успешное значение, не затрагивая failure.

Result<UserDto> dtoResult = userResult.Map(user => new UserDto(user));

Bind

Используется, когда следующий шаг уже возвращает Result.

Result<OrderDto> result = GetUser(id)
    .Bind(CheckAccess)
    .Map(user => new OrderDto(user));

Ensure

Проверяет дополнительное условие над успехом.

Result<string> result = GetName()
    .Ensure(name => !string.IsNullOrWhiteSpace(name), "Name is empty");

Tap

Выполняет побочный эффект на успехе, не меняя результат.

var result = CreateOrder(command)
    .Tap(order => logger.LogInformation("Created order {Id}", order.Id));

TapError

Выполняет побочный эффект на ошибке.

var result = CreateOrder(command)
    .TapError(error => logger.LogWarning("Order creation failed: {Error}", error));

MapError

Преобразует ошибку, не меняя успешный результат.

Result<User, string> domainResult = CreateUser(command);

Result<User, ApiError> apiResult = domainResult.MapErrorTo(error => new ApiError(error));

Асинхронные версии

Библиотека поддерживает async-composition:

  • MatchAsync
  • MapAsync
  • BindAsync
  • EnsureAsync
  • TapAsync
  • TapErrorAsync

Они доступны:

  • для самих Result / Result<T> / Result<T,E> / UnitResult<E>;
  • и для Task<Result...> через extension methods.

Пример async-pipeline

var result = await LoadUserAsync(id)
    .BindAsync(CheckAccessAsync)
    .EnsureAsync(user => repository.IsActiveAsync(user.Id), "User is not active")
    .MapAsync(user => BuildDtoAsync(user))
    .TapAsync(dto => audit.WriteAsync(dto));

Пример async-finalization

var httpResult = await result.MatchAsync(
    onSuccess: value => Task.FromResult($"OK: {value}"),
    onFailure: error => Task.FromResult($"ERROR: {error}"));

DefaultConfigureAwait

Для async-методов используется глобальная настройка Result.DefaultConfigureAwait.

Если она равна true, библиотека будет использовать ConfigureAwait(true), если falseConfigureAwait(false).

Result.DefaultConfigureAwait = false;

Это позволяет централизованно настроить поведение async-цепочек в зависимости от характера приложения.

Когда использовать Map, а когда Bind

Очень короткое правило:

  • Map: если шаг имеет форму T -> K
  • Bind: если шаг имеет форму T -> Result<K>

Пример:

// Map: обычное преобразование
Result<string> text = Result.Success(10)
    .Map(x => x.ToString());

// Bind: шаг сам может завершиться ошибкой
Result<int> parsed = Result.Success("42")
    .Bind(text => int.TryParse(text, out var value)
        ? Result.Success(value)
        : Result.Failure<int>("Parse failed"));

Когда использовать Result<T>, а когда Result<T, E>

Result<T>

Используй, если строковой ошибки достаточно.

Подходит для:

  • небольших библиотек;
  • validation layer;
  • простых сервисов;
  • пользовательских сообщений.

Result<T, E>

Используй, если ошибка является полноценной частью модели.

Подходит для:

  • domain errors;
  • API error models;
  • ошибок с кодом и метаданными;
  • межслойных преобразований ошибок.

Пример полного pipeline

Result<OrderDto> result = Result.Try(() => LoadOrder(id))
    .Ensure(order => order is not null, "Order not found")
    .Bind(CheckPermissions)
    .Map(order => new OrderDto(order))
    .Tap(dto => logger.LogInformation("DTO prepared"));

string output = result.Match(
    onSuccess: dto => $"OK: {dto.Id}",
    onFailure: error => $"ERROR: {error}");

Установка

Если библиотека опубликована как NuGet-пакет:

dotnet add package ResultType

Или подключи проект напрямую через ProjectReference.

Лицензия

MIT

Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  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 is compatible.  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 is compatible.  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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net10.0

    • No dependencies.
  • net6.0

    • No dependencies.
  • net7.0

    • No dependencies.
  • net8.0

    • No dependencies.
  • net9.0

    • No dependencies.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on Ti-Soft.Results:

Package Downloads
Ti-Soft.StringFunctions

Библиотека функций для работы со строками: проверка баланса скобок, нормализация строки, парсинг и форматирование диапазонов целых чисел, согласование существительных с числом, перевод чисел в слова (прописью) и обратно, склонение числительных по падежам, порядковые числительные, дата и денежная сумма прописью на русском языке.

Ti-Soft.SearchEngine

Лёгкая embedded-библиотека для нечёткого и фонетического поиска по строковым полям БД на русском языке с Result-based API.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.0.0 673 3/28/2026
1.3.1 117 3/25/2026
1.3.0 118 3/25/2026