结果对象而不是输出参数

时间:2016-03-03 17:31:13

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

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

目前,我们的大多数方法都有一个'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)
}

我很确定这里唯一的好处是我们不必处理'out'参数,但是我们不考虑哪些缺点?

3 个答案:

答案 0 :(得分:2)

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

然而,这是退一步的好时机,并询问你是否真的在做正确的事情。这种方法的合同似乎很奇怪。我期望一个删除某些东西的方法返回void,因为删除是一种效果,而不是一个值的产生。我希望在失败的情况下,失败状态将存储在抛出的异常中,而不是存储在消息列表中。

答案 1 :(得分:1)

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

我个人更喜欢给定示例中的out实现,因为您的方法往往遵循try-parse pattern。 try-parse模式通过返回bool告诉您成功,并在out参数中提供result / info对象。你的命名在这种情况下有点不对劲。而不是删除,最好将方法命名为TryDelete

答案 2 :(得分:0)

签出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<T>通过添加T Value { get; }属性扩展了上述内容。
  2. (T, IDomainResult)之类的方法中返回ValueTuple

所有这些都可以通过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处检查样本。都是你的。