【问题标题】:Result Object instead of out parameters结果对象而不是输出参数
【发布时间】:2016-03-03 17:31:12
【问题描述】:

我的一位同事提出了一个有趣的想法,但我们都不确定可能出现的并发症。

目前我们的大多数方法都有一个“out”参数来返回消息列表(成功、错误等)。就这样……

public bool Delete(int id, out List<UIMessage> uiMessages)
{
    //Delete stuff
    bool wasDeleteSuccessful = //set bool here
    List<UIMessages> uiMessages = //Set messages here

    return wasDeleteSuccessful
}

我们正在考虑返回一个具有 T 类型属性和 List 属性的新对象的想法。就这样……

public ResultObject<bool> Delete(int id)
{
    //Delete stuff
    bool wasDeleteSuccessful = //set bool here
    List<UIMessages> uiMessages = //Set messages here

    return new ResultObject<bool>(wasDeleteSuccessful, uiMessages)
}

我很确定这里唯一的好处是我们不必处理“输出”参数,但我们没有考虑哪些缺点?

【问题讨论】:

  • 这个想法并不新鲜。检查this 实现和用例。
  • 考虑到List&lt;&gt; 是一个引用类型,调用者可以安全地传入一个初始化的实例,然后该方法将添加到它。只是一种选择。
  • @PoweredByOrange 哦,对……说得通
  • 有些人可以使用out 参数,有些人则不行。我会说这只是一种更清洁的方式来做你已经在做的事情。我不会将此标记为主要基于意见,因为它确实询问了潜在问题,但请记住,您的问题有点抽象。
  • @user2023116 我会说programmers.stackexchange.com 是一个更合适的姊妹网站,可以解决有关软件设计实践的问题。

标签: c# asp.net generics error-handling out


【解决方案1】:

通常最好返回值而不是改变变量。通过返回值而不是改变变量,您可以获得在不能改变变量的上下文中使用您的方法的能力。例如:删除是异步的主要候选者,因为它可能是高延迟操作。改变调用者变量的方法很难实现异步。

但是,现在是退后一步并首先询问您是否真的在做正确的事情的好时机。这种方法的契约似乎很奇怪。我希望删除某些内容的方法返回 void,因为删除是一种效果,而不是值的产生。我希望在失败的情况下,失败状态将存储在抛出的异常中,而不是消息列表中。

【讨论】:

  • 我认为删除方法返回 bool 并不奇怪;这种方法在集合中很常见(例如,HashSet<T>.Remove,因为用户只关心集合是否排除了对象,而不是对象被删除。这在并发集合中也很常见。
  • @EricLippert 如果删除失败是一种常见(非例外)情况,您是否仍然建议使用异常或返回带有失败原因的对象?我这样说是因为我越来越多地看到人们使用异常来控制正常合理操作流程的“趋势”(也许删除是一个不好的例子),我讨厌这样,但除了“出于性能原因”(这是在非调试模式下有点小而不真实)或使用语义“异常应该是异常,而不是未完成的正确操作”,我对此没有论据
【解决方案2】:

没有主要缺点,只是新结果对象不必要的额外复杂性包含与out 参数的先前实现完全相同的信息。如果这些方法被执行很多次,你可能会更好(性能和内存方面)实例化更少的对象,因此你的第一个(out)实现。

我个人更喜欢给定示例中的 out 实现,因为您的方法倾向于遵循try-parse pattern。尝试解析模式通过返回布尔值来通知您成功,并在输出参数中为您提供结果/信息对象。在这种情况下,您的命名有点错误。而不是Delete,最好将方法命名为TryDelete

【讨论】:

    【解决方案3】:

    查看为该问题提供解决方案的 DomainResult NuGet 包(需要 .NET Standard 2)。

    它的核心是IDomainResult(类似于你的ResultObject)和属性:

    IReadOnlyCollection<string> Errors { get; } // Collection of error messages if any
    bool IsSuccess { get; }                     // Flag, whether the current status is successful or not
    DomainOperationStatus Status { get; }       // Current status of the domain operation: Success, Error, NotFound
    

    从这里开始,您的方法中有 2 个用于返回对象的选项:

    1. 一个通用的IDomainResult&lt;T&gt;,通过添加T Value { get; } 属性扩展了上面的那个。
    2. 从方法中返回ValueTuple,例如(T, IDomainResult)

    这一切都添加了 50 多种扩展方法,例如

    // Successful result with an int
    (value, state) = IDomainResult.Success(10);        // value = 10; state.Status is 'Success'
    // The same but wrapped in a task
    var res = IDomainResult.SuccessTask(10);           // res is Task<(int, IDomainResult)>
    
    // Error message
    IDomainResult = IDomainResult.Error("Ahh!");       // res.Status is 'Error' and res.Errors = new []{ "Ahh!" }
    // Error when expected an int
    (value, state) = IDomainResult.Error<int>("Ahh!"); // value = 0, state.Status is 'Error' and state.Errors = new []{ "Ahh!" }
    

    例如:

    public async Task<(InvoiceResponseDto, IDomainResult)> GetInvoice(int invoiceId)
    {
        if (invoiceId < 0)
            // Returns a validation error
            return IDomainResult.Error<InvoiceResponseDto>("Try harder");
    
        var invoice = await DataContext.Invoices.FindAsync(invoiceId);
        
        if (invoice == null)
            // Returns a Not Found response
            return IDomainResult.NotFound<InvoiceResponseDto>();
    
        // Returns the invoice
        IDomainResult.Success(invoice);
    }
    

    或者如果你反对ValueTuple,那么更传统的方法签名:

    public async Task<IDomainResult<InvoiceResponseDto>> GetInvoice(int invoiceId)
    {
        if (invoiceId < 0)
            // Returns a validation error
            return DomainResult.Error<InvoiceResponseDto>("Try harder");
        ...
    }
    

    它还有 20 多个扩展,可以将基于 IDomainResult 的类型转换为对应的 IActionResult,以便从 WebAPI 控制器方法返回。

    https://github.com/AKlaus/DomainResult 上查看示例。都是你的。

    【讨论】:

      猜你喜欢
      • 2018-04-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-11-19
      相关资源
      最近更新 更多