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

C# 返回命名ValueTuple和成功标志的函数,c#,c#-7.0,c#-8.0,C#,C# 7.0,C# 8.0,我一次又一次地遇到这种情况,我希望函数同时返回命名的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.na

我一次又一次地遇到这种情况,我希望函数同时返回命名的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`
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
丑陋,需要定义结果,到处都需要
result.Value


选项2:

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`
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
这使用了。
{}
是一个属性模式,它实际上没有指定任何属性,因此它匹配任何不为
null的对象


不过,IMO认为它的可读性不是很好。有人讨论过如何添加新模式来改进这样的情况,例如
不为null

还有一种方法可以改进在out声明中分解元组的情况,它可以让您编写:

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

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

} 
你可以做:

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

if (!(MyFunc() is {} myFuncRes))
{
    // Error handling
    return;
}
// use myFuncRes.name1 and myFuncRes.name2
这使用了。
{}
是一个属性模式,它实际上没有指定任何属性,因此它匹配任何不为
null的对象


不过,IMO认为它的可读性不是很好。有人讨论过如何添加新模式来改进这样的情况,例如
不为null

还有一种方法可以改进在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);
    }
}

为什么不使用嵌套元组呢

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);
    }
}
设计用于表示一组变量(如函数中的局部变量),而不是实体

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

最初的方法首先应该是:

bool MyFunc(T1 param, out Type1 name1, out Type2 name2)
(bool success, Type1 name1, Type2 name2) MyFunc(T1 param)
如果不能使用out参数(例如,如果它是异步方法),则应为:

bool MyFunc(T1 param, out Type1 name1, out Type2 name2)
(bool success, Type1 name1, Type2 name2) MyFunc(T1 param)
设计用于表示一组变量(如函数中的局部变量),而不是实体

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

最初的方法首先应该是:

bool MyFunc(T1 param, out Type1 name1, out Type2 name2)
(bool success, Type1 name1, Type2 name2) MyFunc(T1 param)
如果不能使用out参数(例如,如果它是异步方法),则应为:

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

返回这些结果的典型方法是:

(bool success, string name1, string name2) MyFunc(bool param)
{
    return param ? (true,"A","B")
                 :default;
}
bool
的默认值为false,因此我可以懒散地返回
default

您可以将模式匹配与
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)
}
通过在元组中添加
error
字段并匹配null,可以在C#中完成相同的操作:

(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}");
}

或者,使用开关表达式:

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

返回这些结果的典型方法是:

(bool success, string name1, string name2) MyFunc(bool param)
{
    return param ? (true,"A","B")
                 :default;
}
bool
的默认值为false,因此我可以懒散地返回
default

您可以将模式匹配与
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)
}
通过在元组中添加
error
字段并匹配null,可以在C#中完成相同的操作:

(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}");
}

或者,使用开关表达式:

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

如果返回值为false,则元组不应用于任何内容,则这种方法可能会起作用。当这种情况发生时,我们只提供一个默认元组(应立即忽略),而不是试图通过可为null的类型来传达它为null

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);
}

如果返回值为false,则元组不应用于任何内容,则这种方法可能会起作用。当这种情况发生时,我们只提供一个默认元组(应立即忽略),而不是试图通过可为null的类型来传达它为null

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
    }

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

我确实找到了一种更好的方法:

    (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
    }

这相当清晰简洁,不会引入任何不必要的变量

这是元组中的成功标志部分吗?似乎你也想要吃蛋糕。你可以用一个单独的元组来包装你的名称1/2元组,该元组还包含结果,但这仍然需要进行分解。看看是否包含成功标志您可以将模式匹配与success常量一起使用,例如
if(MyFunc()为(true,var someValue)){usethit(someValue);}
成功标志是元组的一部分吗?似乎你也想吃蛋糕。你可以用一个单独的元组来包装你的name1/2元组,该元组还包括result,但这仍然需要解构。看看是否包含成功标志,你可以使用模式匹配成功常量,例如
If(MyFunc()是(true,var someValue){usethit(someValue);}
感谢您的扩展。模式匹配的有趣用法…我必须看看它。这似乎并不完全正确