【问题标题】:TryParse dilemma-Dealing with out parameters [closed]TryParse 困境 - 处理无参数 [关闭]
【发布时间】:2015-05-31 07:44:28
【问题描述】:

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

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

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

Tuple<Exception,T> TryParseT(object obj)

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

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

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

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

【问题讨论】:

  • 没有得到更多基于此的意见。我自己会采用可为空的方法。
  • 我采用的方法是拥有一个从各种解析器返回的Option&lt;T&gt; 类。然后它返回None 或值。更多详情请查看github.com/DavidArno/SuccincT/tree/master/SuccincT/…
  • @DavidArno 我不明白为什么null 是邪恶的。如果处理得当并记录在案,我认为它没有任何问题。是的,它更容易出错,但这取决于开发人员。
  • @YuvalItzchakov,我向您推荐 (a) “空参考:十亿美元的错误” (infoq.com/presentations/…) 和 (b) null 完全违反直觉的事实,如证据所示无休止的“这个 NullReferenceException 是什么意思?”新手在本站询问。 Null 可以说是编程语言设计中最大的错误。
  • @YuvalItzchakov null 的问题在于它不尊重它所宣传的合同。一个空的string 不是string,即使它说是。这就是为什么使用Option&lt;T&gt; 方法更好的原因,因为在编写良好的库中,如果结果是null,它不会让您使用该值。如果您有兴趣,我已经写了更多关于此的内容:github.com/louthy/language-ext

标签: c# functional-programming out


【解决方案1】:

本质上问题在于,要遵循函数式编程方法,您应该始终为输入值提供返回值。所以返回void 路线不是要走的路。您需要返回一个值,该值可以代表成功(并保存成功结果)和失败(并且不保存结果)。

最接近的是您返回包含异常的Tuple。然而,一旦你得到它,你就没有'基础设施'来可靠地处理Tuple。所以围绕它的代码脚手架将被重复。

看看this librarylanguage-ext。它使用Option&lt;T&gt; 的实现来处理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;

它还具有IDictionaryIReadOnlyDictionaryIImmutableDictionaryIImmutableSetTryGetValue 扩展名,而是返回 Option&lt;T&gt;,并且可以像上面一样使用。

【讨论】:

    【解决方案2】:

    最优雅的方法是

    int Parse(string value)
    

    Tryxxxx 方法仅存在于名为性能的实现细节。如果您正在寻求优雅,您可以使用 Parse 方法并通过快速失败来处理任何错误。 您可以改为返回一个元组,但这将花费堆上的额外分配,因为元组是一种引用类型。

    在性能方面(如果您关心的话)更好的解决方案是 aKeyValuePair。但是它隐藏了(像元组一样)通用数据类型背后的语义,这对于代码清晰度来说并不是最佳的。比定义元组的第一个布尔值包含故障状态的约定更好的表示故障的方法是定义您自己的数据类型。

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

    【讨论】:

    • 投反对票,因为它建议一个由于完全无异常的原因引发异常的方法,就像 Parse 所做的那样,不是完全不优雅的。
    • @Davide Arno:优雅取决于你的个人风格。在元组内按值返回异常明显违反了应该如何使用异常。如果希望 FXCop 不会让您的时尚签到成功。不合时宜的编码指南是有原因的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-12
    • 1970-01-01
    • 2011-11-27
    • 1970-01-01
    • 1970-01-01
    • 2010-10-05
    相关资源
    最近更新 更多