函数返回命名的 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
}
这相当清晰简洁,没有引入任何不需要的变量
我 运行 一次又一次地陷入这种情况,我想要一个函数来 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
}
这相当清晰简洁,没有引入任何不需要的变量