一、前言
最近在做项目的过程中,涉及到一个函数要求返回多个值的问题,经过一番折腾,问题得到了解决。但我觉得这个问题具有代表性,以后还会遇到这样的问题,本着学习的目的,通过查阅一些资料,来对函数返回多个值得解决方法进行一下总结,以期能有所收获,并与各位园友探讨学习。
二、函数返回多个值方法(一)使用out返回多个值
在介绍out使函数返回多个值之前,我们首先来对C#中的ref关键字和out关键字进行一下介绍
我们在调用函数时,通常都是向函数中传递值,函数获得的时我们传递的这些值的拷贝,函数执行时,使用的的是这些拷贝,函数执行结束,这些拷贝也就自然消失,而原来的值不会受到影响。当然这是通常情况,另一种情况是我们向函数传递参数,也就是使用ref(引用)和out(输出)。
有时候,我们需要改变原来变量的值,这时候我们就需要向函数传递变量引用,引用是一个变量并可以访问原变量的值,修改引用就修改了原来变量的值。这里需要对这一原理进行一下解释,这个原理其实很简单,即为:变量的值存储在计算机内存中,可以创建一个引用指向计算机中的变量在内存中的位置,所以当引用被修改时,其实修改的是内存中的值,因此变量的值也就被修改了,但我们调用一个含有引用参数的函数时,函数中的参数将指向传递给函数的变量,从而导致修改参数变量的值的同时原来变量的值也就被修改。这样一解释,是不是现在就更明白了。话不多说,下面,我们将通过实例的方式,来演示ref关键字的用法。
使用ref进行参数引用传递的实例代码如下:
namespace ShareResourceContract { public class MultipleOutPut { public double MathCalculation(ref double x) { x = x * x; return x; } } } static void Main(string[] args) { MultipleOutPut output = new MultipleOutPut(); double a = 5; //使用ref传递变量引用必须在函数体外初始化,不然编译不会通过 double b = 3; Console.WriteLine("Before:a={0},b={1}", a, b); output.MathCalculation(ref a); Console.WriteLine("After:a={0},b={1}", a, b); Console.ReadLine(); }
输出结果如下:
通过以上实例,我们可以看到,原来变量a的值已经被修改了。
通过以上的介绍,相信大家对变量引用传递参数已经有了一个更深层次的理解。接下来我们继续介绍,有时候,我们希望一个函数返回多个值,虽然使用ref也可以实现,但C#专门提供了一个专门out关键字来处理函数返回多值得问题,以下我们同样用实例的方式来对其展开介绍。
使用out关键字返回多个值得实例代码如下:
namespace ShareResourceContract { public class MultipleOutPut { public double MathCalculation(ref double x) { x = x * x; return x; } public void MulOutPut(double x,out double add,out double subtract,out double squar) { add = x + x; subtract = x - 5; squar = x * x; } } } static void Main(string[] args) { MultipleOutPut output = new MultipleOutPut(); double x = 8; double add = 0; double subtract = 0; double squar = 0; Console.WriteLine("Before:x={0}", x); Console.WriteLine("Before:add={0}", add); Console.WriteLine("Before:subtract={0}", subtract); Console.WriteLine("Before:squar={0}", squar); output.MulOutPut(x, out add, out subtract, out squar); Console.WriteLine("After:x={0}", x); Console.WriteLine("After:add={0}", add); Console.WriteLine("After:subtract={0}", subtract); Console.WriteLine("After:squar={0}", squar); Console.ReadLine(); }
输出结构如下:
通过以上实例可以看出,使用out关键字,使函数返回了多个返回值,
下面我们来对ref和out的区别进行以下说明:
1、用ref引用的变量在调用函数之前必须初始化,相当于在函数外部实例化,函数里面只是对这个对象进行修改的过程;
2、而out指定的参数可以不必在函数调用之前初始化,不管有没有在函数外部被初始化,out指定的参数将在函数内部被清空,并在函数内部进行初始化。
3、二者的区别可以简单理解为:“ref有进有出,而out只出不进”。
三、函数返回多个值方法(二)使用Tuple返回多个值
通过以上介绍,相信大家已经对C#使用关键字ref和out使函数返回多个值有了一个深层次的理解,其实C#中除了使用ref和out关键字使函数返回多个值之外,还有一种方法也能实现同样的目的,那就是使用Tuple(元组)返回多个值。话不多说,接下来,我们将就使用Tuple使函数返回多个值展开详细介绍。
首先,我们先来回顾一下Tuple,Tuple是C# 4.0时出的新特性,.Net Framework 4.0以上版本可用。元组(Tuple)是一种数据结构,具有特定数量和元素序列。创建对应的元组可以表示一组数据,例如创建具有StudentCode、Name、Age、Sex四元组数据来存储学生基本信息。
1、创建元组的方法
(1)利用构造函数创建元组(注意:默认情况.Net Framework元组仅支持1到7个元组元素,如果有8个元素或者更多,需要使用Tuple的嵌套和Rest属性去实现)
var tupleTest1 = new Tuple<int, int, int, int, int, int,int>(1, 2, 3, 4, 5, 6,7); Console.WriteLine($"Item 1: {tupleTest1.Item1}, Item 7: {tupleTest1.Item7}"); var tupleTest2= new Tuple<int, int, int, int, int, int, int, Tuple<int, int, int>>(1, 2, 3, 4, 5, 6, 7, new Tuple<int, int, int>(8, 9, 10)); Console.WriteLine($"Item 1: {tupleTest2.Item1}, Item 10: {tupleTest2.Rest.Item3}");
(2)利用Tuple静态方法构建元组(最多支持八个元素)
var tupleTest13=Tuple.Create<int, int, int, int, int, int,int>(1, 2, 3, 4, 5, 6,7); Console.WriteLine($"Item 1: {tupleTest1.Item1}, Item 7: {tupleTest1.Item7}"); var tupleTest4=Tuple.Create<int, int, int, int, int, int, int, int>(1, 2, 3, 4, 5, 6, 7, 8); Console.WriteLine($"Item 1: {tupleTest2.Item1}, Item 8: {tupleTest2.Rest.Item1}");
2、代替out从函数中返回多个值
通常,我们需要从函数中返回多个值时,使用out关键字输出参数。学习了Tuple之后,就可以使用Tuple代替out实现从函数中返回多个值了。例如:
using System; namespace ShareResourceContract { public class MultipleOutPut { public Tuple<double ,string> MulOutPutByTuple(double x) { double addResult = 0; var resultMessage = ""; addResult = x + 8; resultMessage = "变量加了8之后的和为"+ addResult; return new Tuple<double, string>(addResult, resultMessage); } } }
static void Main(string[] args) { MultipleOutPut output = new MultipleOutPut(); double x = 5; var tupList=output.MulOutPutByTuple(x); Console.WriteLine("函数返回值:addResult={0},resultMessage={1}", tupList.Item1,tupList.Item2); Console.ReadLine(); }
输出结果如下:
从上面输出结果我们可以看到,函数返回了两数相加之后的结果以及结果提示两个信息。元组(Tuple)除了以上所述用途外,还有一个用途作者认为也非常有用,即用于单参数方法的多值传递(当函数参数仅是一个Object类型时,可以使用元组实现传递多个参数值),下面给出具体实例:
static void WriteStudentInfo(Object student) { var studentInfo = student as Tuple<string,string, int, string>; Console.WriteLine($"Student Information: StudentCode[{studentInfo.Item1}], Name [{studentInfo.Item2}], Age[{studentInfo.Item3}],Sex[{studentInfo.Item4}]"); } static void RunTest() { var t = new System.Threading.Thread(new System.Threading.ParameterizedThreadStart(WriteStudentInfo)); t.Start(new Tuple<string,string, int, string>("20200068","lilei", 20, "男")); while (t.IsAlive) { System.Threading.Thread.Sleep(50); } }
Tuple元组的不足之处:
- 问元素的时候只能通过ItemX去访问,使用前需要明确元素顺序,属性名字没有实际意义,不方便记忆;
- 最多有八个元素,要想更多只能通过最后一个元素进行嵌套扩展;
- Tuple是一个引用类型,不像其它的简单类型一样是值类型,它在堆上分配空间,在CPU密集操作时可能有太多的创建和分配工作。
鉴于以上的不足,C# 7.0中引入了一个新的ValueTuple(值元组)类型,园友北田在这篇(https://www.cnblogs.com/unity3ds/p/11641582.html)博文中对ValueTuple(值元组)进行了详细的介绍,大家可以参考。