← Назад к вопросам

Приведи пример реализации возврата состояния

1.0 Junior🔥 132 комментариев
#Другое

Комментарии (2)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Пример реализации возврата состояния в C#

В C# существует несколько основных паттернов и подходов для возврата состояния, каждый из которых применяется в разных контекстах. Рассмотрим наиболее распространенные варианты с примерами.

1. Возврат через кортежи (ValueTuple)

Кортежи, появившиеся в C# 7.0, позволяют легко возвращать несколько значений, включая состояние операции:

public (bool Success, string ErrorMessage, int? Result) DivideNumbers(int a, int b)
{
    if (b == 0)
        return (false, "Division by zero is not allowed", null);
    
    return (true, null, a / b);
}

// Использование
var (success, error, result) = DivideNumbers(10, 2);
if (success)
    Console.WriteLine($"Result: {result}");
else
    Console.WriteLine($"Error: {error}");

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

2. Классы-обертки результата операции

Создание специализированных классов для возврата состояния - более объектно-ориентированный подход:

public class OperationResult<T>
{
    public bool IsSuccess { get; }
    public string ErrorMessage { get; }
    public T Value { get; }
    
    private OperationResult(bool isSuccess, T value, string errorMessage)
    {
        IsSuccess = isSuccess;
        Value = value;
        ErrorMessage = errorMessage;
    }
    
    public static OperationResult<T> Success(T value) => 
        new OperationResult<T>(true, value, null);
    
    public static OperationResult<T> Failure(string errorMessage) => 
        new OperationResult<T>(false, default, errorMessage);
}

// Использование
public OperationResult<int> CalculateDiscount(int price, int discountPercent)
{
    if (price <= 0)
        return OperationResult<int>.Failure("Price must be positive");
    
    if (discountPercent < 0 || discountPercent > 100)
        return OperationResult<int>.Failure("Discount must be between 0 and 100");
    
    int finalPrice = price - (price * discountPercent / 100);
    return OperationResult<int>.Success(finalPrice);
}

var result = CalculateDiscount(1000, 20);
if (result.IsSuccess)
    Console.WriteLine($"Final price: {result.Value}");
else
    Console.WriteLine($"Error: {result.ErrorMessage}");

Преимущества: типобезопасность, расширяемость, семантическая ясность. Недостатки: больше кода, необходимость создания классов.

3. Паттерн Result из функционального программирования

Более продвинутый подход, вдохновленный функциональным программированием:

public abstract class Result<T>
{
    public abstract bool IsSuccess { get; }
    public abstract T Value { get; }
    public abstract string Error { get; }
    
    public static Result<T> Ok(T value) => new SuccessResult<T>(value);
    public static Result<T> Fail(string error) => new FailureResult<T>(error);
}

public class SuccessResult<T> : Result<T>
{
    private readonly T _value;
    
    public SuccessResult(T value) => _value = value;
    
    public override bool IsSuccess => true;
    public override T Value => _value;
    public override string Error => throw new InvalidOperationException("Success result has no error");
}

public class FailureResult<T> : Result<T>
{
    private readonly string _error;
    
    public FailureResult(string error) => _error = error;
    
    public override bool IsSuccess => false;
    public override T Value => throw new InvalidOperationException("Failure result has no value");
    public override string Error => _error;
}

// Использование с методами расширения
public static class ResultExtensions
{
    public static Result<TOut> Bind<TIn, TOut>(
        this Result<TIn> result,
        Func<TIn, Result<TOut>> func)
    {
        return result.IsSuccess ? func(result.Value) : Result<TOut>.Fail(result.Error);
    }
    
    public static Result<TOut> Map<TIn, TOut>(
        this Result<TIn> result,
        Func<TIn, TOut> func)
    {
        return result.IsSuccess 
            ? Result<TOut>.Ok(func(result.Value))
            : Result<TOut>.Fail(result.Error);
    }
}

public Result<int> ParseInt(string input)
{
    return int.TryParse(input, out var number)
        ? Result<int>.Ok(number)
        : Result<int>.Fail($"Cannot parse '{input}' as integer");
}

public Result<int> DoubleValue(int value) => Result<int>.Ok(value * 2);

// Композиция операций
var result = ParseInt("10")
    .Bind(DoubleValue)
    .Map(x => x + 5);

if (result.IsSuccess)
    Console.WriteLine($"Result: {result.Value}");

Преимущества: поддержка функциональной композиции, монадические операции (Bind, Map), исключение исключений для ошибок. Недостатки: сложность для новичков, больше boilerplate кода.

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

Традиционный подход для критических ошибок:

public class OrderProcessor
{
    public decimal CalculateTotal(Order order)
    {
        if (order == null)
            throw new ArgumentNullException(nameof(order));
        
        if (order.Items.Count == 0)
            throw new InvalidOperationException("Order must contain at least one item");
        
        if (order.Customer.IsBlocked)
            throw new CustomerBlockedException(order.Customer.Id);
        
        return order.Items.Sum(item => item.Price * item.Quantity);
    }
}

public class CustomerBlockedException : Exception
{
    public int CustomerId { get; }
    
    public CustomerBlockedException(int customerId) 
        : base($"Customer with ID {customerId} is blocked")
    {
        CustomerId = customerId;
    }
}

// Обработка
try
{
    var total = processor.CalculateTotal(order);
    Console.WriteLine($"Total: {total}");
}
catch (CustomerBlockedException ex)
{
    Console.WriteLine($"Customer blocked: {ex.Message}");
}
catch (InvalidOperationException ex)
{
    Console.WriteLine($"Invalid operation: {ex.Message}");
}

Преимущества: стандартный механизм .NET, прерывание потока выполнения, stack trace для отладки. Недостатки: накладные расходы на performance, не для бизнес-логики (лучше для exceptional ситуаций).

5. Использование OneOf библиотек

Для сложных сценариев с множественными типами результатов:

// Пример с библиотекой OneOf
// Установка: Install-Package OneOf

public class ValidationService
{
    public OneOf<SuccessResult, NotFoundError, ValidationError> ValidateUser(User user)
    {
        if (user == null)
            return new NotFoundError("User not found");
        
        if (string.IsNullOrEmpty(user.Email))
            return new ValidationError("Email is required");
        
        if (user.Age < 18)
            return new ValidationError("User must be at least 18 years old");
        
        return new SuccessResult(user.Id);
    }
}

public record SuccessResult(int UserId);
public record NotFoundError(string Message);
public record ValidationError(string Message);

// Использование
var result = ValidateUser(user);

result.Switch(
    success => Console.WriteLine($"User ID: {success.UserId}"),
    notFound => Console.WriteLine($"Not found: {notFound.Message}"),
    validation => Console.WriteLine($"Validation failed: {validation.Message}")
);

Рекомендации по выбору подхода:

  • Для простых сценариев с 1-2 возвращаемыми значениями - используйте кортежи
  • Для бизнес-логики с необходимостью обработки ошибок - классы-обертки или паттерн Result
  • Для критических, исключительных ситуаций - исключения
  • Для сложных сценариев с множественными типами результатов - OneOf или аналоги
  • В ASP.NET Core Web API - используйте встроенный IActionResult с Ok(), BadRequest(), NotFound() и т.д.

Наиболее современным и гибким подходом считается паттерн Result, особенно в комбинации с CQRS и MediatR, где он позволяет создавать чистые, тестируемые обработчики команд и запросов без побочных эффектов в виде исключений для бизнес-ошибок.