结果对象而不是输出参数
本文关键字:输出 参数 对象 结果 | 更新日期: 2023-09-27 18:37:11
我的一位同事提出了一个有趣的想法,但我们都不确定可能出现的并发症。
目前,我们的大多数方法都有一个"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"参数,但我们没有考虑哪些缺点?
通常最好返回值而不是改变变量。通过返回值而不是改变变量,您可以在不得改变变量的上下文中使用您的方法。例如,考虑:删除是异步的主要候选项,因为它可能是高延迟操作。改变调用方变量的方法很难异步。
但是,这将是退后一步并询问您是否真的在做正确的事情的好时机。这种方法的契约似乎很奇怪。我希望删除某些内容的方法返回 void,因为删除是一种效果,而不是值的产生。我希望在发生故障时,失败状态将存储在抛出的异常中,而不是存储在消息列表中。
没有主要缺点,只是新结果对象包含与之前使用out
参数实现完全相同的信息的不必要的额外复杂性。如果这些方法被大量执行,你可能会更好(性能和内存方面),实例化的对象更少,因此你的第一个(out)实现。
我个人更喜欢给定示例中的 out 实现,因为您的方法倾向于遵循 try-parse 模式。try-parse 模式通过返回布尔值来通知您成功,并在 out 参数中为您提供结果/info 对象。在这种情况下,您的命名有点错误。与其删除,不如将方法命名为 TryDelete
查看为该问题提供解决方案的 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 个选项:
- 通过添加属性来扩展上述
T Value { get; }
的泛型IDomainResult<T>
。 - 从方法返回值元组,如
(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 查看示例。这一切都是你的。