函数返回命名的 ValueTuple 和成功标志

Function returning named ValueTuple and success flag

我 运行 一次又一次地陷入这种情况,我想要一个函数来 return 一个命名的 ValueTuple 和一个成功标志

我能想到的所有选项都不是很好:(

选项 1:

bool MyFunc(T1 param, out (Type1 name1, Type2 name2)? result)

if (!MyFunc(param, out var result)) { .. .. my error handling }

.. do stuff with `result.Value.name1` and `result.Value.name2`

丑陋,需要定义结果并且到处都需要 result.Value


选项 2:

bool MyFunc(T1 param, out (Type1 name1, Type2 name2)? result)

if (!MyFunc(param, out var result)) { .. .. my error handling }
var (name1, name2) = result.Value;

.. do stuff with name1, name2

丑陋,需要定义结果并显式破坏元组

选项 3:

(Type1 name1, Type2 name2)? MyFunc(Type3 param) {}

var myFuncRes = myFunc(param);
if (myFuncRes is null) { .. my error handling }

.. do stuff with myFuncRes.Value.name1 etc 

丑陋,需要 myFuncRes.Value 或显式破坏元组


我希望能够做的是:

(Type1 name1, Type2 name2)? MyFunc(T1 param)

if (var myFuncRes = MyFunc(param) is null) { .. .. my error handling }

.. do stuff with `myFuncRes.name1` and `myFuncRes.name2` directly !

这是我的明确意图..但这是不允许的


是否有更好的方法或通用的解决方案?

(注意我对这里抛出异常不感兴趣)

你可以这样做:

(Type1 name1, Type2 name2)? MyFunc() {...} 

if (!(MyFunc() is {} myFuncRes))
{
    // Error handling
    return;
}
// use myFuncRes.name1 and myFuncRes.name2

这使用 pattern matching. {} is a property pattern which doesn't actually specify any properties, and so it matches any object which is not null. See this answer

不过,IMO,它的可读性并不高。有关于添加新模式以改善此类情况的讨论,例如 is not null.

还有 a proposal 改进了在 out 声明中解构元组的情况,这样你就可以写:

bool MyFunc(out (Type1 name1, Type2 name2) result) {...} 

if (!MyFunc(out var (name1, name2)) 
{

} 

为什么不使用嵌套元组?

static ((int name1, int name2) result, bool success) Test()
{
    return ((1, 1), true);
}

static void Main()
{
    var x = Test();
    if (x.success)
    {
        Console.WriteLine("Your numbers are {0} and {1}", x.result.name1, x.result.name2);
    }
}

C# tuples 旨在表示一组变量(如函数中的局部变量)而不是实体。

如果您将元组视为一个单元,您很可能做错了什么。

原来的方法,首先应该是:

bool MyFunc(T1 param, out Type1 name1, out Type2 name2)

如果你不能有out参数(例如,如果它是一个异步方法),它应该是:

(bool success, Type1 name1, Type2 name2) MyFunc(T1 param)

return 这些结果的典型方法是:

(bool success, string name1, string name2) MyFunc(bool param)
{
    return param ? (true,"A","B")
                 :default;
}

bool的默认值是false,我可以偷懒直接returndefault

您可以使用 is 的模式匹配来检查和提取单个干净行中的值:

if(MyFunc(true) is (true,var name1,var name2)){
 Console.WriteLine("AAA");
}

if(MyFunc(false) is (true,var name3,var name4)){
 Console.WriteLine("Should never enter here");
}

这将只打印 "AAA"

走风格

Go 目前以相同的方式将元组用于多值结果,因为它还没有例外。这会导致复杂的控制流并引入忽略错误的可能性。不过,按照惯例,最后一个元组字段不是标志,而是一条错误消息:

func f1(arg int) (int, error) {
    if arg == 42 {
        return -1, errors.New("can't work with 42")
    }
    return arg + 3, nil
}

...

if r, e := f1(i); e != nil {
    fmt.Println("f1 failed:", e)
} else {
    fmt.Println("f1 worked:", r)
}

在 C# 中可以通过在元组中添加一个 error 字段并匹配 null 来完成同样的事情:

(int i,string? error) F1(int arg)
{
    return arg==42? (-1,"Can't work with 42")
                    :(arg+3,null);
}

...


var (i,error) =F1(42);
if (error is string err)
{
    Console.WriteLine($"F1 failed: {err}");
}
else
{
    Console.WriteLine($"F1 worked: {i}");
}

switch (F1(42)) 
{
    case (_,string error):
        Console.WriteLine($"F1 failed: {error}");
        break;
    case (int i,null):
        Console.WriteLine($"F1 worked: {i}");
        break;
}

或者,使用 switch 表达式:

var output = F1(43) switch {
    (_,string error)=>$"F1 failed: {error}",
    (int i,null)    =>$"F1 worked: {i}"
};
Console.WriteLine(output);

如果在 return 值为 false 时元组不应用于任何用途,则此方法可能有效。当发生这种情况时,我们只是提供一个默认元组(应该立即忽略)而不是试图通过可空类型来传达它是空的。

static bool Test(T1 param, out (Type1 name1, Type2 name2) result)
{
    try
    {
        //Successful case
        result = ( X, Y );
        return true;
    }
    catch
    {
        //Failure case
        result = (default(Type1), default(Type1));
        return false;
    }
}

现在调用者只需要写:

if (Test(param, out var result))
{
    Console.WriteLine("Your results were {0} and {1}", result.name1, result.name2);
}

我确实找到了更好的感觉:

    (bool success, (string name1, string name2)) MyFunc() {
      if (DateTime.Now.Year > 2020) return (false, ("", "")); // error 
      return (true, ("some value", "some value"));
    }

    void F() {
      if (!(MyFunc() is (true, var (name1, name2)))) return;
      Console.WriteLine(name1);
      .. do stuff with name1 and name2
    }

这相当清晰简洁,没有引入任何不需要的变量