TryParse困境——处理输出参数

本文关键字:参数 输出 处理 TryParse 困境 | 更新日期: 2023-09-27 17:51:18

我从来不喜欢outref参数。当我看到它们的实际操作时,它们给我的感觉是设计有些混乱。

我认为唯一的例外是所谓的TryXXX模式,它返回一个布尔值作为函数结果(无论一切都很好还是出了问题)和一个out参数的实际结果,直到我今天读了这篇文章,它让我认为如果有一个更好的模式来实现这种方法。

我认为我们可以有一个函数返回多个结果(或者如文章所说的一个元组)

Tuple<Exception,T> TryParseT(object obj)

或接受成功回调函数的函数:

void TryParseT(object obj,Action<T> success)

问题是,从功能设计的角度来看,哪一个更好?

更新:换句话说,我想知道这两个函数中哪一个更符合函数式编程原则,为什么?

TryParse困境——处理输出参数

最优雅的方法是

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>的实现来改善TryParseout问题。

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, IImmutableDictionaryIImmutableSet,它也有TryGetValue扩展,而不是返回Option<T>,可以如上所述使用