如何从C#中的函数返回多个值?

How can I return multiple values from a function in C#?

我读了这个问题的C++版本,但并没有真正理解它。

如果可以的话,有人能解释清楚吗?如何解释?


使用.NET 4.0+的元组:

例如:

1
2
3
4
public Tuple<int, int> GetMultipleValue()
{
     return Tuple.Create(1,2);
}

具有两个值的元组的属性为Item1Item2


既然C 7已经发布,您可以使用新的包含元组语法

1
2
3
4
5
(string, string, string) LookupName(long id) // tuple return type
{
    ... // retrieve first, middle and last from data storage
    return (first, middle, last); // tuple literal
}

然后可以这样使用:

1
2
var names = LookupName(id);
WriteLine($"found {names.Item1} {names.Item3}.");

您还可以为元素提供名称(因此它们不是"item1"、"item2"等)。您可以通过在签名或返回方法中添加名称来完成此操作:

1
(string first, string middle, string last) LookupName(long id) // tuple elements have names

1
return (first: first, middle: middle, last: last); // named tuple elements in a literal

它们也可以解构,这是一个非常好的新功能:

1
(string first, string middle, string last) = LookupName(id1); // deconstructing declaration

查看此链接以查看有关可以做什么的更多示例:)


你可以用三种不同的方法

1。参考/输出参数

使用REF:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static void Main(string[] args)
{
    int a = 10;
    int b = 20;
    int add = 0;
    int multiply = 0;
    Add_Multiply(a, b, ref add, ref multiply);
    Console.WriteLine(add);
    Console.WriteLine(multiply);
}

private static void Add_Multiply(int a, int b, ref int add, ref int multiply)
{
    add = a + b;
    multiply = a * b;
}

使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static void Main(string[] args)
{
    int a = 10;
    int b = 20;
    int add;
    int multiply;
    Add_Multiply(a, b, out add, out multiply);
    Console.WriteLine(add);
    Console.WriteLine(multiply);
}

private static void Add_Multiply(int a, int b, out int add, out int multiply)
{
    add = a + b;
    multiply = a * b;
}

2。结构/类

使用Stutt:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
struct Result
{
    public int add;
    public int multiply;
}
static void Main(string[] args)
{
    int a = 10;
    int b = 20;
    var result = Add_Multiply(a, b);
    Console.WriteLine(result.add);
    Console.WriteLine(result.multiply);
}

private static Result Add_Multiply(int a, int b)
{
    var result = new Result
    {
        add = a * b,
        multiply = a + b
    };
    return result;
}

使用类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Result
{
    public int add;
    public int multiply;
}
static void Main(string[] args)
{
    int a = 10;
    int b = 20;
    var result = Add_Multiply(a, b);
    Console.WriteLine(result.add);
    Console.WriteLine(result.multiply);
}

private static Result Add_Multiply(int a, int b)
{
    var result = new Result
    {
        add = a * b,
        multiply = a + b
    };
    return result;
}

三。元组

元组类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static void Main(string[] args)
{
    int a = 10;
    int b = 20;
    var result = Add_Multiply(a, b);
    Console.WriteLine(result.Item1);
    Console.WriteLine(result.Item2);
}

private static Tuple<int, int> Add_Multiply(int a, int b)
{
    var tuple = new Tuple<int, int>(a + b, a * b);
    return tuple;
}

C 7元组

1
2
3
4
5
6
7
8
9
10
11
12
13
static void Main(string[] args)
{
    int a = 10;
    int b = 20;
    (int a_plus_b, int a_mult_b) = Add_Multiply(a, b);
    Console.WriteLine(a_plus_b);
    Console.WriteLine(a_mult_b);
}

private static (int a_plus_b, int a_mult_b) Add_Multiply(int a, int b)
{
    return(a + b, a * b);
}


你不能用C来做这个。您可以做的是拥有一个out参数,或者返回您自己的类(如果您希望它是不可变的,则返回struct)。

使用out参数

1
2
3
4
public int GetDay(DateTime date, out string name)
{
  // ...
}

使用自定义类(或结构)

1
2
3
4
5
6
7
8
9
10
public DayOfWeek GetDay(DateTime date)
{
  // ...
}

public class DayOfWeek
{
  public int Day { get; set; }
  public string Name { get; set; }
}


如果要返回多个值,可以返回包含要返回的值的类/结构,或者在参数上使用"out"关键字,如下所示:

1
2
3
public void Foo(int input, out int output1, out string output2, out string errors) {
    // set out parameters inside function
}


上一张海报是对的。不能从C方法返回多个值。但是,您有几个选择:

  • 返回包含多个成员的结构
  • 返回类的实例
  • 使用输出参数(使用out或ref关键字)
  • 使用字典或键值对作为输出

这里的利弊往往很难弄清楚。如果返回结构,请确保它很小,因为结构是值类型并在堆栈上传递。如果您返回一个类的实例,这里有一些设计模式,您可能希望使用这些模式来避免引起问题-可以修改类的成员,因为C通过引用传递对象(您没有像在VB中那样使用ByVal)。

最后,您可以使用输出参数,但我会将其限制在只有两个(如3个或更少)参数的情况下使用——否则情况会变得难看和难以维护。此外,输出参数的使用可能是敏捷性的一个约束,因为每次需要向返回值添加内容时,方法签名都必须更改,而返回结构或类实例时,可以添加成员而不修改方法签名。

从体系结构的角度来看,我建议不要使用键值对或字典。我发现这种类型的编码需要消耗方法的代码中的"秘密知识"。它必须提前知道密钥将是什么以及值的含义,如果负责内部实现的开发人员改变了字典或kvp的创建方式,那么它很容易在整个应用程序中创建故障级联。


返回类实例或使用out参数。以下是out参数的示例:

1
2
3
4
5
void mymethod(out int param1, out int param2)
{
    param1 = 10;
    param2 = 20;
}

这样称呼它:

1
2
3
int i, j;
mymethod(out i, out j);
// i will be 20 and j will be 10


不,不能从C(对于低于C 7的版本)中的函数返回多个值,至少不能像在Python中那样。

但是,有两种选择:

您可以返回一个类型为object的数组,其中包含您想要的多个值。

1
2
3
4
private object[] DoSomething()
{
    return new [] { 'value1', 'value2', 3 };
}

您可以使用out参数。

1
2
3
4
5
6
private string DoSomething(out string outparam1, out int outparam2)
{
    outparam1 = 'value2';
    outparam2 = 3;
    return 'value1';
}

有几种方法可以做到这一点。您可以使用ref参数:

1
int Foo(ref Bar bar) { }

这将传递对函数的引用,从而允许函数修改调用代码堆栈中的对象。虽然这在技术上不是"返回"值,但它是一种让函数执行类似操作的方法。在上面的代码中,函数将返回一个int,并(可能)修改bar

另一种类似的方法是使用out参数。out参数与ref参数相同,并具有附加的编译器强制规则。此规则是,如果向函数传递out参数,则该函数必须在返回之前设置其值。除此之外,out参数的工作方式与ref参数类似。

最后一种方法(大多数情况下最好)是创建一个封装两个值的类型,并允许函数返回:

1
2
3
4
5
6
7
class FooBar
{
    public int i { get; set; }
    public Bar b { get; set; }
}

FooBar Foo(Bar bar) { }

最后一种方法简单易懂。


在C 4中,您将能够使用对元组的内置支持来轻松处理这一问题。

同时,有两种选择。

首先,可以使用ref或out参数为参数赋值,这些参数将被传递回调用例程。

这看起来像:

1
void myFunction(ref int setMe, out int youMustSetMe);

第二,您可以将返回值包装到一个结构或类中,并将它们作为该结构的成员传递回去。keyValuePair适用于2-对于2个以上的类或结构,需要自定义类或结构。


在C 7中有一个新的Tuple语法:

1
2
3
4
static (string foo, int bar) GetTuple()
{
    return ("hello", 5);
}

您可以将此作为记录返回:

1
2
3
var result = GetTuple();
var foo = result.foo
// foo =="hello"

还可以使用新的解构器语法:

1
2
(string foo) = GetTuple();
// foo =="hello"

但是,要小心串行化,所有这些都是语法上的糖分——在实际编译的代码中,这将是一个Tupel(根据接受的答案),用Item1Item2代替foobar。这意味着序列化(或反序列化)将使用这些属性名。

因此,对于序列化,声明一个记录类并返回它。

C 7中的新功能还包括改进的out参数语法。您现在可以声明out内联,这在某些情况下更适合:

1
2
3
if(int.TryParse("123", out int result)) {
    // Do something with result
}

但是,大多数情况下,您将在.NET自己的库中使用它,而不是在您自己的函数中使用它。


Some of these answers stated that use out parameter but I recommend
not using this due to they don’t work with async methods. See
this for more information.

其他的答案是使用tuple,我也建议使用c 7.0中引入的新特性。

1
2
3
4
5
6
7
8
(string, string, string) LookupName(long id) // tuple return type
{
    ... // retrieve first, middle and last from data storage
    return (first, middle, last); // tuple literal
}

var names = LookupName(id);
WriteLine($"found {names.Item1} {names.Item3}.");

更多信息可在这里找到。


你可以试试这个"keyValuePair"

1
2
3
4
5
6
7
8
9
private KeyValuePair<int, int> GetNumbers()
{
  return new KeyValuePair<int, int>(1, 2);
}


var numbers = GetNumbers();

Console.WriteLine("Output : {0}, {1}",numbers.Key, numbers.Value);

输出:

产量:1, 2


类、结构、集合和数组可以包含多个值。输出和参考参数也可以在函数中设置。在动态语言和函数语言中,通过元组返回多个值是可能的,但在C中不可能。


以下是基本的Two方法:

1)使用"EDOCX1"〔3〕作为参数您也可以对4.0和次要版本使用"out"。

"out"示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using System;

namespace out_parameter
{
  class Program
   {
     //Accept two input parameter and returns two out value
     public static void rect(int len, int width, out int area, out int perimeter)
      {
        area = len * width;
        perimeter = 2 * (len + width);
      }
     static void Main(string[] args)
      {
        int area, perimeter;
        // passing two parameter and getting two returning value
        Program.rect(5, 4, out area, out perimeter);
        Console.WriteLine("Area of Rectangle is {0}\t",area);
        Console.WriteLine("Perimeter of Rectangle is {0}\t", perimeter);
        Console.ReadLine();
      }
   }
}

输出:

矩形的面积为20

矩形周长为18

*注:*out关键字描述参数,这些参数的实际变量位置被复制到被调用方法的堆栈中,在堆栈中可以重写这些相同的位置。这意味着调用方法将访问更改的参数。

2)江户十一〔14〕号

元组示例:

使用Tuple返回多个数据类型值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
using System;

class Program
{
    static void Main()
    {
    // Create four-item tuple; use var implicit type.
    var tuple = new Tuple<string, string[], int, int[]>("perl",
        new string[] {"java","c#" },
        1,
        new int[] { 2, 3 });
    // Pass tuple as argument.
    M(tuple);
    }

    static void M(Tuple<string, string[], int, int[]> tuple)
    {
    // Evaluate the tuple's items.
    Console.WriteLine(tuple.Item1);
    foreach (string value in tuple.Item2)
    {
        Console.WriteLine(value);
    }
    Console.WriteLine(tuple.Item3);
    foreach (int value in tuple.Item4)
    {
        Console.WriteLine(value);
    }
    }
}

产量

1
2
3
4
5
6
perl
java
c#
1
2
3

注:tuple的使用在框架4.0及以上是有效的,Tuple类型是class类型。它将被分配到内存中托管堆的单独位置。一旦创建了Tuple,就不能更改其fields的值。这使得Tuple更像一个struct


主要有两种方法。1。使用输出/参考参数2。返回对象数组


接受委托的方法可以向调用方提供多个值。这借用了我这里的答案,使用了哈达斯接受的答案中的一点。

1
2
3
4
5
delegate void ValuesDelegate(int upVotes, int comments);
void GetMultipleValues(ValuesDelegate callback)
{
    callback(1, 2);
}

调用方提供lambda(或命名函数),IntelliSense通过从委托复制变量名来提供帮助。

1
2
3
4
GetMultipleValues((upVotes, comments) =>
{
     Console.WriteLine($"This post has {upVotes} Up Votes and {comments} Comments.");
});

以OOP方式使用这样的类:

1
2
3
4
5
6
7
8
9
10
class div
{
    public int remainder;

    public int quotient(int dividend, int divisor)
    {
        remainder = ...;
        return ...;
    }
}

函数成员返回大多数调用者主要感兴趣的商。另外,它将剩余部分存储为一个数据成员,调用方随后可以很容易地访问它。

通过这种方式,您可以拥有许多额外的"返回值",如果您实现数据库或网络调用,这非常有用,在这种情况下可能需要大量的错误消息,但只有在发生错误时才有用。

我也在OP提到的C++问题中输入了这个解决方案。


1
2
3
4
5
6
7
<--Return more statements like this you can -->

public (int,string,etc) Sample( int a, int b)  
{
    //your code;
    return (a,b);  
}

您可以接收类似的代码

1
(c,d,etc) = Sample( 1,2);

我希望它能奏效。


在本文中,您可以使用三个选项作为上述文章。

键值对是最快的方法。

出局是在第二个。

Tuple是最慢的。

不管怎样,这取决于什么是最适合您的场景。


您可以使用动态对象。我认为它的可读性比元组好。

1
2
3
4
5
6
7
8
9
10
11
12
static void Main(string[] args){
    var obj = GetMultipleValues();
    Console.WriteLine(obj.Id);
    Console.WriteLine(obj.Name);
}

private static dynamic GetMultipleValues() {
    dynamic temp = new System.Dynamic.ExpandoObject();
    temp.Id = 123;
    temp.Name ="Lorem Ipsum";
    return temp;
}


如何做到这一点:

1)keyValuePair(最佳性能-0.32 ns):

1
2
3
4
    KeyValuePair<int, int> Location(int p_1, int p_2, int p_3, int p_4)
    {                
         return new KeyValuePair<int,int>(p_2 - p_1, p_4-p_3);
    }

2)tuple-5.40纳秒:

1
2
3
4
    Tuple<int, int> Location(int p_1, int p_2, int p_3, int p_4)
    {
          return new Tuple<int, int>(p_2 - p_1, p_4-p_3);
    }

3)输出(1.64 ns)或参考4)创建自己的自定义类/结构

ns->纳秒

引用:多个返回值。


C的未来版本将包括命名的元组。看看这个Channel9演示会https://channel9.msdn.com/events/build/2016/b889

跳到13:00就可以了。这将允许以下内容:

1
2
3
4
5
6
7
(int sum, int count) Tally(IEnumerable<int> list)
{
// calculate stuff here
return (0,0)
}

int resultsum = Tally(numbers).sum

(视频中不完整的示例)


也可以使用OperationResult

1
2
3
4
5
6
7
8
9
public OperationResult DoesSomething(int number1, int number2)
{
// Your Code
var returnValue1 ="return Value 1";
var returnValue2 ="return Value 2";

var operationResult = new OperationResult(returnValue1, returnValue2);
return operationResult;
}


你可以试试这个

1
2
3
4
public IEnumerable<string> Get()
 {
     return new string[] {"value1","value2" };
 }


现在,程序员需要时间和不可理解的方法。简单、有效、快速的解决方案:

1
2
3
4
private int[] SumAndSub(int A, int B)
{
    return new[] { A + B , A - B };
}

在某个地方使用它;

1
2
3
var results = SumAndSub(20, 5);
int sum = results[0];
int sub = results[0];