What's the difference between the 'ref' and 'out' keywords?
我正在创建一个函数,我需要在其中传递一个对象,以便它可以被函数修改。有什么区别:
1 | public void myFunction(ref MyClass someClass) |
和
1 | public void myFunction(out MyClass someClass) |
我应该使用哪一个?为什么?
因此,虽然
假设Dom出现在Peter的隔间里,是关于TPS报告的备忘录。
如果dom是ref参数,他会有一份备忘录的打印件。
如果多姆不同意,他会让彼得打印一份新的备忘录,让他随身携带。
我要试着解释一下:
我认为我们理解价值类型是如何工作的?值类型为(int、long、struct等)。当您在没有引用命令的情况下将它们发送到函数时,它会复制数据。在函数中对该数据所做的任何操作都只影响副本,而不是原始数据。REF命令发送实际数据,任何更改都将影响函数外部的数据。
好的,关于混淆部分,参考类型:
允许创建引用类型:
1 |
当您新建某个对象时,将创建两个部分:
现在,当您将某个对象发送到一个没有引用的方法中时,它会复制引用指针,而不是数据。现在你有了这个:
1 2 | (outside method) reference1 => someobject (inside method) reference2 => someobject |
指向同一对象的两个引用。如果使用reference2修改someobject的属性,它将影响reference1指向的相同数据。
1 2 | (inside method) reference2.Add("SomeString"); (outside method) reference1[0] =="SomeString" //this is true |
如果您将reference2设为空或指向新数据,则不会影响reference1或数据reference1指向。
1 2 3 4 5 6 |
现在,当您通过引用方法发送某个对象时会发生什么?对someObject的实际引用将被发送到该方法。所以您现在只有一个对数据的引用:
1 2 | (outside method) reference1 => someobject; (inside method) reference1 => someobject; |
但这意味着什么?它的作用与发送某个对象完全相同,而不是通过引用,除了两件主要事情:
1)当方法内的引用为空时,方法外的引用将为空。
1 2 | (inside method) reference1 = null; (outside method) reference1 == null; //true |
2)现在可以将引用指向完全不同的数据位置,而函数外部的引用现在将指向新的数据位置。
1 2 | (inside method) reference1 = new List<string>(); (outside method) reference1.Count == 0; //this is true |
裁判进出。
在满足您的需求的地方,您应该优先使用
出:
在C中,一个方法只能返回一个值。如果要返回多个值,可以使用out关键字。out修饰符作为引用返回。最简单的答案是,关键字"out"用于从方法中获取值。
裁判:
在C中,当您将一个值类型(如int、float、double等)作为参数传递给方法参数时,它是通过value传递的。因此,如果修改参数值,它不会影响方法调用中的参数。但是,如果用"ref"关键字标记参数,它将反映在实际变量中。
延伸狗,猫的例子。带有ref的第二个方法更改了调用方引用的对象。因此"猫"!!!!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | public static void Foo() { MyClass myObject = new MyClass(); myObject.Name ="Dog"; Bar(myObject); Console.WriteLine(myObject.Name); // Writes"Dog". Bar(ref myObject); Console.WriteLine(myObject.Name); // Writes"Cat". } public static void Bar(MyClass someObject) { MyClass myTempObject = new MyClass(); myTempObject.Name ="Cat"; someObject = myTempObject; } public static void Bar(ref MyClass someObject) { MyClass myTempObject = new MyClass(); myTempObject.Name ="Cat"; someObject = myTempObject; } |
除以下差异外,
ref 变量在使用前必须初始化。out 变量不需要赋值即可使用out 参数必须由使用它的函数视为未赋值。因此,我们可以在调用代码中使用初始化的out 参数,但是当函数执行时,该值将丢失。
由于正在传入引用类型(类),因此不需要使用
例子:
1 2 3 4 5 6 7 8 9 10 11 12 | public void Foo() { MyClass myObject = new MyClass(); myObject.Name ="Dog"; Bar(myObject); Console.WriteLine(myObject.Name); // Writes"Cat". } public void Bar(MyClass someObject) { someObject.Name ="Cat"; } |
只要传入一个类,如果您想更改方法中的对象,就不必使用
对于那些以身作则的人(像我一样),这里是安东尼·科里索夫所说的。
我已经创建了一些引用、out和其他的最小示例来说明这一点。我不介绍最佳实践,只是举例来了解不同之处。
https://gist.github.com/2upmedia/6d98a57b68d849ee7091
"Baker"
这是因为第一个将字符串引用改为指向"baker"。更改引用是可能的,因为您通过ref关键字(=>对字符串引用的引用)传递了它。第二个调用获取对字符串的引用的副本。
一开始字符串看起来有点特别。但字符串只是一个引用类,如果您定义
1 | string s ="Able"; |
然后s是对包含文本"able"的字符串类的引用!同一变量的另一个赋值通过
1 | s ="Baker"; |
不更改原始字符串,只创建一个新实例,让我们指向该实例!
您可以通过下面的小代码示例来尝试:
1 2 3 4 | string s ="Able"; string s2 = s; s ="Baker"; Console.WriteLine(s2); |
你期待什么?您将得到的仍然是"能够"的,因为您只是将S中的引用设置为另一个实例,而s2指向原始实例。
编辑:字符串也是不可变的,这意味着根本没有修改现有字符串实例的方法或属性(您可以尝试在文档中找到一个方法或属性,但不会找到任何:-)。所有字符串操作方法都返回一个新的字符串实例!(这就是为什么在使用StringBuilder类时通常会获得更好的性能的原因)
Ref表示Ref参数中的值已经设置,方法可以读取和修改它。使用ref关键字与表示调用者负责初始化参数的值相同。
out告诉编译器对象的初始化是函数,函数必须分配给out参数。不允许不分配。
REF和OUT工作就像传递引用和指针通过C++一样。
对于引用,参数必须声明并初始化。
对于out,参数必须声明,但可以初始化,也可以不初始化
1 2 3 4 5 | double nbr = 6; // if not initialized we get error double dd = doit.square(ref nbr); double Half_nbr ; // fine as passed by out, but inside the calling method you initialize it doit.math_routines(nbr, out Half_nbr); |
出:RETURN语句只能用于从函数返回一个值。但是,使用输出参数,可以从函数返回两个值。输出参数与引用参数类似,只是它们将数据从方法中传递出来,而不是传递到方法中。
下面的示例说明了这一点:
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 | using System; namespace CalculatorApplication { class NumberManipulator { public void getValue(out int x ) { int temp = 5; x = temp; } static void Main(string[] args) { NumberManipulator n = new NumberManipulator(); /* local variable definition */ int a = 100; Console.WriteLine("Before method call, value of a : {0}", a); /* calling a function to get the value */ n.getValue(out a); Console.WriteLine("After method call, value of a : {0}", a); Console.ReadLine(); } } } |
裁判:引用参数是对变量内存位置的引用。当通过引用传递参数时,与值参数不同,不会为这些参数创建新的存储位置。引用参数表示与提供给方法的实际参数相同的内存位置。
在C中,使用ref关键字声明引用参数。下面的示例演示了这一点:
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 31 32 33 34 35 | using System; namespace CalculatorApplication { class NumberManipulator { public void swap(ref int x, ref int y) { int temp; temp = x; /* save the value of x */ x = y; /* put y into x */ y = temp; /* put temp into y */ } static void Main(string[] args) { NumberManipulator n = new NumberManipulator(); /* local variable definition */ int a = 100; int b = 200; Console.WriteLine("Before swap, value of a : {0}", a); Console.WriteLine("Before swap, value of b : {0}", b); /* calling a function to swap the values */ n.swap(ref a, ref b); Console.WriteLine("After swap, value of a : {0}", a); Console.WriteLine("After swap, value of b : {0}", b); Console.ReadLine(); } } } |
创作时间:
(1)我们创建了调用方法
(2)它创建一个列表对象(它是一个引用类型对象)并将其存储在变量
1 2 3 4 5 |
运行期间:
(3)运行时在堆栈上的00处分配内存,其宽度足以存储地址(00=
(4)运行时在内存位置ff处的堆上创建一个列表对象(例如,所有这些地址都是sakes)
(5)运行时将对象的起始地址存储在00(或者换句话说,将列表对象的引用存储在指针
回到创作时间:
(6)然后我们将list对象作为参数
1 2 3 4 5 6 7 8 |
运行期间:
(7)运行时启动被调用方法的调用例程,作为其一部分,检查参数类型。
(8)在找到引用类型后,它在04处的堆栈上分配一个内存,用于为参数变量
(9)然后,它也将值存储在其中。
(10)运行时在内存位置004的堆上创建一个列表对象,并用该值替换04中的FF(或取消引用原始列表对象并指向此方法中的新列表对象)。
00中的地址没有更改,保留了对FF的引用(或原始
ref关键字是一个编译器指令,用于跳过(8)和(9)的运行时代码生成,这意味着不会为方法参数分配堆。它将使用原始的00指针在ff对对象进行操作。如果原始指针未初始化,运行时将停止抱怨它无法继续,因为变量未初始化
out关键字是一个编译器指令,它与ref几乎相同,只在(9)和(10)处做了细微的修改。编译器期望参数未初始化,并将继续(8)、(4)和(5)在堆上创建对象,并将其起始地址存储在参数变量中。不会引发未初始化的错误,存储的任何以前的引用都将丢失。
它们几乎是相同的-唯一的区别是,作为out参数传递的变量不需要初始化,并且使用ref参数的方法必须将其设置为某个值。
1 2 | int x; Foo(out x); // OK int y; Foo(ref y); // Error |
Ref参数用于可能被修改的数据,Out参数用于已经使用返回值的函数(例如int.typarse)的附加输出数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | public static void Main(string[] args) { //int a=10; //change(ref a); //Console.WriteLine(a); // Console.Read(); int b; change2(out b); Console.WriteLine(b); Console.Read(); } // static void change(ref int a) //{ // a = 20; //} static void change2(out int b) { b = 20; } |
你可以检查这个代码,它将描述你的完全不同当使用"ref"时,它意味着u已经初始化了int/string
但是当你使用"out"时无论您是否初始化int/string,它都可以在这两种情况下工作。但是u必须初始化函数中的int/string
裁判:ref关键字用于将参数作为引用传递。这意味着当该参数的值在方法中更改时,它会反映在调用方法中。使用ref关键字传递的参数在传递给被调用方法之前,必须在调用方法中初始化。
出:out关键字还用于传递像ref关键字这样的参数,但是可以在不给参数赋值的情况下传递该参数。使用out关键字传递的参数必须在被调用方法中初始化,然后才能返回到调用方法。
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 | public class Example { public static void Main() { int val1 = 0; //must be initialized int val2; //optional Example1(ref val1); Console.WriteLine(val1); Example2(out val2); Console.WriteLine(val2); } static void Example1(ref int value) { value = 1; } static void Example2(out int value) { value = 2; } } /* Output 1 2 |
方法重载中的ref和out
在方法重载中不能同时使用ref和out。但是,在运行时对ref和out的处理是不同的,但在编译时对它们的处理是相同的(clr在为ref和out创建il时不区分这两者)。
从接收参数的方法的角度来看,
从调用者的角度来看,在许多情况下,C在使用
1 2 3 4 5 6 7 8 | struct MyStruct { ... myStruct(IDictionary<int, MyStruct> d) { d.TryGetValue(23, out this); } } |
如果
注意,用vb.net编写的构造函数与用c_编写的构造函数不同,它不假设被调用的方法是否会修改任何
如果要将参数作为引用传递,则应在将参数传递给函数之前对其进行初始化,否则编译器本身将显示错误。但是,如果参数为out,则在将对象参数传递给方法之前不需要对其进行初始化。可以在调用方法本身中对对象进行初始化。
下面我展示了一个使用ref和out的示例。现在,你们都将被清除关于裁判和出局。
在下面提到的示例中,当我评论//MyRefObj=New MyClass name="Ref Outside Called!"!"行,将得到一个错误,说明"使用未分配的局部变量‘myrefobj’",但在out中没有这样的错误。
在哪里使用ref:当我们使用in参数调用一个过程时,相同的参数将用于存储该过程的输出。
在哪里使用out:当我们调用一个没有in参数的过程时,相同的param将用于从该过程返回值。还要注意输出
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 | public partial class refAndOutUse : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { myClass myRefObj; myRefObj = new myClass { Name ="ref outside called!! <br/>" }; myRefFunction(ref myRefObj); Response.Write(myRefObj.Name); //ref inside function myClass myOutObj; myOutFunction(out myOutObj); Response.Write(myOutObj.Name); //out inside function } void myRefFunction(ref myClass refObj) { refObj.Name ="ref inside function <br/>"; Response.Write(refObj.Name); //ref inside function } void myOutFunction(out myClass outObj) { outObj = new myClass { Name ="out inside function <br/>" }; Response.Write(outObj.Name); //out inside function } } public class myClass { public string Name { get; set; } } |
请注意,在函数内部传递的引用参数是直接处理的。
例如,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | public class MyClass { public string Name { get; set; } } public void Foo() { MyClass myObject = new MyClass(); myObject.Name ="Dog"; Bar(myObject); Console.WriteLine(myObject.Name); // Writes"Dog". } public void Bar(MyClass someObject) { MyClass myTempObject = new MyClass(); myTempObject.Name ="Cat"; someObject = myTempObject; } |
这将写狗,而不是猫。因此,您应该直接处理某个对象。
我可能不擅长于此,但字符串(即使它们在技术上是引用类型,并且位于堆中)肯定是通过值传递的,而不是引用?
1 2 3 4 5 6 7 8 9 | string a ="Hello"; string b ="goodbye"; b = a; //attempt to make b point to a, won't work. a ="testing"; Console.WriteLine(b); //this will produce"hello", NOT"testing"!!!! |
这就是为什么您需要引用的原因如果您希望更改存在于生成它们的函数的作用域之外,那么就不需要传递引用。
据我所知,您只需要结构/值类型和字符串本身的引用,因为字符串是一种引用类型,它假装它是但不是值类型。
不过,我可能完全错了,我是新来的。