TryParse困境——处理输出参数
本文关键字:参数 输出 处理 TryParse 困境 | 更新日期: 2023-09-27 17:51:18
我从来不喜欢out
和ref
参数。当我看到它们的实际操作时,它们给我的感觉是设计有些混乱。
我认为唯一的例外是所谓的TryXXX模式,它返回一个布尔值作为函数结果(无论一切都很好还是出了问题)和一个out参数的实际结果,直到我今天读了这篇文章,它让我认为如果有一个更好的模式来实现这种方法。
我认为我们可以有一个函数返回多个结果(或者如文章所说的一个元组)
Tuple<Exception,T> TryParseT(object obj)
或接受成功回调函数的函数:
void TryParseT(object obj,Action<T> success)
问题是,从功能设计的角度来看,哪一个更好?
更新:换句话说,我想知道这两个函数中哪一个更符合函数式编程原则,为什么?
最优雅的方法是
int Parse(string value)
Tryxxxx方法只存在于名为performance的实现细节中。如果您追求优雅,您可以使用Parse方法并通过快速失败来处理任何错误。你也可以返回一个元组,但这将在堆上花费额外的分配,因为tuple是一个引用类型。
一个更好的解决方案在性能方面(如果你关心)将是aKeyValuePair。但是它隐藏了(像元组一样)泛型数据类型背后的语义,这对于代码清晰度来说不是最佳的。与定义元组的第一个bool值包含失败状态的约定相比,更好的失败信号方式是定义自己的数据类型。
struct ParseResult<T>
{
public bool Success { get; private set; }
public T Value { get; private set; }
public ParseResult(T value, bool success):this()
{
Value = value;
Success = success;
}
}
class Program
{
static ParseResult<int> TryParse(string s)
{
int lret = 0;
if (int.TryParse(s, out lret))
{
return new ParseResult<int>(lret, true);
}
else
{
return new ParseResult<int>(lret, false);
}
}
static void Main(string[] args)
{
string test = "1";
var lret = TryParse(test);
if( lret.Success )
{
Console.WriteLine("{0}", lret.Value);
}
}
}
这种方法仍然非常有效,并且以分配便宜的容器对象为代价为您节省了out参数。
本质上的问题是,要遵循函数式编程方法,您应该始终为输入值提供返回值。因此,返回void
路线不是要走的路。您需要返回一个可以表示成功(并保存成功的结果)和失败(并且不保存结果)的值。
最接近的是您返回包含异常的Tuple
的地方。然而,一旦你得到了Tuple
,你就没有可靠地处理它的"基础设施"。因此围绕它的代码搭建将被重复。
看看这个库language-ext
。利用Option<T>
的实现来改善TryParse
的out
问题。
string inp = "123";
// Attempts to parse the value, uses 0 if it can't
int value1 = parseInt(inp).IfNone(0);
// Functional alternative to above
// Attempts to parse the value, uses 0 if it can't
int value2 = ifNone(parseInt(inp), 0);
// Attempts to parse the value and then pattern matches the result
int value3 = parseInt(inp).Match(
Some: x => x * 2,
None: () => 0
);
// Functional alternative to above
// Attempts to parse the value and then pattern matches the result
int value4 = match( parseInt(inp),
Some: x => x * 2,
None: () => 0
);
标准库还允许您检查某些内容是否有效:
if( parseInt(inp) )
return 1;
else
return 0;
和允许不实际提取值的比较:
if( parseInt(inp) == 123 )
return 123;
else
return 0;
以及逻辑运算:
var allValid = parseInt(x) && parseInt(y) && parseInt(z);
var someValid = parseInt(x) || parseInt(y) || parseInt(z);
最后是LINQ表达式,它通常可以消除对if-then-else或匹配的需要:
var res = from x in parseInt(inp1)
from y in parseInt(inp2)
from z in parseInt(inp3)
select x + y + z;
对于IDictionary
, IReadOnlyDictionary
, IImmutableDictionary
和IImmutableSet
,它也有TryGetValue
扩展,而不是返回Option<T>
,可以如上所述使用