C# 处理out参数的TryParse困境

C# 处理out参数的TryParse困境,c#,functional-programming,out,C#,Functional Programming,Out,我从来都不喜欢out和ref参数。当我看到它们在运行时,它们给我一种设计混乱的感觉 我认为唯一的例外是所谓的TryXXX模式,它返回一个布尔值作为函数结果(无论一切正常还是出现问题),并为实际结果返回一个out参数,直到今天我读到这篇文章,它让我思考是否有更好的模式来实现这种方法 我认为我们可以有一个返回多个结果的函数(或者正如文章所说的元组) Tuple-TryParseT(object-obj) 或接受回调函数以获得成功的函数: void TryParseT(object obj,Acti

我从来都不喜欢
out
ref
参数。当我看到它们在运行时,它们给我一种设计混乱的感觉

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

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

Tuple-TryParseT(object-obj)
或接受回调函数以获得成功的函数:

void TryParseT(object obj,Action<T> success)
void TryParseT(对象对象,动作成功)
问题是,从功能设计的角度来看,哪一个更好

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

最优雅的方法是

int Parse(string value)
Tryxxx方法仅适用于名为performance的实现详细信息。如果您正在寻求优雅,您可以使用Parse方法并通过快速失败来处理任何错误。 相反,您可以返回一个元组,但由于元组是引用类型,因此这将在堆上花费额外的分配

在性能方面(如果您愿意的话)更好的解决方案是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);
        }
    }
}
struct ParseResult
{
公共bool成功{get;private set;}
公共T值{get;私有集;}
公共ParseResult(T值,bool success):this()
{
价值=价值;
成功=成功;
}
}
班级计划
{
静态解析结果TryParse(字符串s)
{
int-lret=0;
if(内锥棱镜,外锥棱镜)
{
返回新的解析结果(lret,true);
}
其他的
{
返回新的解析结果(lret,false);
}
}
静态void Main(字符串[]参数)
{
字符串测试=“1”;
var lret=三巴色(试验);
if(lret.Success)
{
Console.WriteLine(“{0}”,lret.Value);
}
}
}

这种方法仍然非常有效,并以分配廉价容器对象为代价,为您节省了out参数

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

与此最接近的是返回一个包含异常的
元组的位置。然而,一旦你拥有了元组,你就没有了可靠地处理元组的“基础设施”。因此,围绕它的代码脚手架将被重复

请看
语言分机
。它使用
选项的实现来解决
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;

它还有
TryGetValue
扩展名,用于
IDictionary
IReadOnlyDictionary
IImmutableDictionary
IImmutableSet
,这些扩展名返回
选项
,并可以如上所述使用。

没有得到更多基于此的意见。我自己也会使用可空的方法。我采用的方法是使用一个
选项
类,该类从各种解析器返回。然后返回
None
或值。查看更多详细信息。@DavidArno我不明白为什么
null
是邪恶的。如果处理得当并记录在案,我看不出有什么问题。是的,它更容易出错,但这取决于开发人员。@YuvalItzchakov,我建议您(a)参考“空引用:十亿美元的错误”()和(b)
Null
这一事实完全违反直觉,这一点可以从该网站上的新手没完没了地问到的“这个空引用异常是什么意思?”中得到证明
Null
可以说是编程语言设计中最大的一个错误。@YuvalItzchakov
Null
的问题在于它不尊重它所宣传的合同。空的
字符串
不是
字符串
,即使它说它是。这就是为什么使用
选项
方法更好,因为在编写良好的库中,如果结果为
null
,则不允许使用该值。如果你感兴趣,我已经写了更多关于这一点的文章:反对票,因为它建议一个方法因为完全非异常的原因抛出异常,就像
Parse
所做的那样,而不是完全不雅观。@Davide Arno:雅观取决于你的个人风格。通过元组内的值返回异常显然违反了异常的使用方式。如果希望FXCop不会让您的时尚签入成功。非柱式编码指南的存在是有原因的。
var res = from x in parseInt(inp1)
          from y in parseInt(inp2)
          from z in parseInt(inp3)
          select x + y + z;