TryParse困境 - 处理参数

时间:2015-05-31 07:44:29

标签: c# functional-programming out

我从不喜欢outref参数。当我看到它们在行动时,它们让我觉得设计上有些混乱。

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

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

Tuple<Exception,T> TryParseT(object obj)

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

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

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

更新: 为了重新解释我的问题,我想知道这两个函数中的哪一个更符合函数式编程原理,为什么?

2 个答案:

答案 0 :(得分:1)

基本上问题是,遵循函数式编程方法,您应该始终为输入值提供返回值。因此返回的void路线不是最佳选择。您需要返回一个可以表示成功(并保持成功结果)和失败(并且不保留结果)的值。

最接近的是您返回Tuple的地方,其中包含例外情况。然而,你没有基础设施&#39;一旦你得到它,就可以可靠地处理Tuple。所以围绕它的代码脚手架将重复。

查看this library language-ext。它涉及使用out的实现来改善TryParse的{​​{1}}问题。

Option<T>

该库还允许您检查某些内容是否有效:

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;

最后LINQ表达式通常可以删除if-then-else或匹配的需要:

var allValid = parseInt(x) && parseInt(y) && parseInt(z);
var someValid = parseInt(x) || parseInt(y) || parseInt(z);

它还有var res = from x in parseInt(inp1) from y in parseInt(inp2) from z in parseInt(inp3) select x + y + z; TryGetValueIDictionaryIReadOnlyDictionary的{​​{1}}个附加信息,而是返回IImmutableDictionary,可以按上述方式使用

答案 1 :(得分:0)

最优雅的方法是

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参数。