Mentioning of ref in C# function parameters
我做了一个函数,我要传递一个字符串数组,比如string[]a a=null在函数定义中,我对它进行了一些更新,然后发现在执行/返回函数时,它的值没有更新。我无法理解C中的哪些内容需要提到ref关键字。它不是总是按引用传递参数吗?为什么需要提到引用?哪些对象需要与引用一起提及才能通过引用?
代码是这样的,只是试图显示我在做什么,我无法更新没有使用ref的值。
1 2 3 4 5 6 7 8 9 | string[] temp = null foo(ref temp); //function definition void foo (ref string[] temp) { temp = {"Hello World","You must be updated now"} } foreach(string s in temp) System.Console.WriteLine(s) |
你在错综复杂的事情上有点错了。如果您传递一个引用对象,那么它将通过值传递引用(继续阅读,希望它开始变得更有意义)。但是,这并不意味着它是通过引用传递的。实际上,默认情况下,C参数是通过值传递的。
这是一篇很好的文章,很好地解释了这一点。
这是一个片段,它解释了当我说你通过值传递引用时我的意思。
In C#, parameters are (by default) passed by value, meaning that
they are implicitly copied when passed to the method. For value-type
parameters, this means physically copying the instance (in the same
way p2 was copied), while for reference-types it means copying a
reference (in the same way f2 was copied)
. 但是,在您的情况下,您将传入对象,然后在方法中使用新引用创建一个新对象。因此,原始引用保持不变,您可以通过执行更新而不是全新的创建来看到这一点。当您显式地说"引用"时,那么现在您正在传递对引用的引用,因此它可以工作,因为您只使用指向引用的指针,并且当您创建新对象时,它将被放置到该引用位置。
正如eouw0o83hf所提到的,如果您正在创建一个全新的对象,那么您应该使用一个
总结:
- 如果它是一个值类型,并且您希望更新该值并使其反映到任何地方,那么您需要使用
ref 。 - 如果它是引用类型
- 如果您希望更新该值以使其反映到所有地方,那么您可以正常地传递它(没有
ref 或out ) - 如果您希望在方法内创建一个全新的实例并将其反映出来,那么应该使用
out 。
- 如果您希望更新该值以使其反映到所有地方,那么您可以正常地传递它(没有
更新
这是一篇详细解释您所要求的内容的msdn文章:)
这是因为您实际上返回了一个全新的对象,而不是修改现有对象的条目。如果要分配新对象,则应使用
下面是一个示例,说明
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 | class Program { static void Main(string[] args) { string[] val; foo(out val); Console.WriteLine(string.Join(",", val)); // Output: 1, 2 bar(ref val); Console.WriteLine(string.Join(",", val)); // Output: modified, 2 bar2(val); Console.WriteLine(string.Join(",", val)); // Output: modified again, 2 Console.Read(); } static void foo(out string[] temp) { temp = new string[]{"1","2"}; } static void bar(ref string[] temp) { temp[0] ="modified"; } static void bar2(string[] temp) { temp[0] ="modified again"; } } |
C始终按值传递参数,除非使用
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 36 | string[] temp = {"zero","one","two" }; MutateByVal(temp); MutateByRef(ref temp); void MutateByVal(string[] arr) { // arr and temp are separate references to the same array // the value of arr (a reference) is a copy of the value of temp // mutate the array referenced by arr arr[1] ="mutated!"; // arr and temp still point at the same array // so both arr and temp now contain {"zero","mutated!","two" } // re-assign arr arr = new[] {"blah","blah","blah" }; // arr and temp now point at different arrays // arr now contains {"blah","blah","blah" } // temp still contains {"zero","mutated!","two" } } void MutateByRef(ref string[] arr) { // arr is an alias for temp // that is, they are two different names for the same reference // mutate the array referenced by arr arr[1] ="mutated!"; // arr and temp are the same reference // so both arr and temp now contain {"zero","mutated!","two" } // re-assign arr arr = new[] {"blah","blah","blah" }; // arr and temp are the same reference // so both arr and temp now contain {"blah","blah","blah" } } |
首先,您的伪代码应该可以工作。但是,在我们开始之前,这里有三件事在起作用:值类型、引用类型和"ref"关键字。
值类型通常是简单的基本类型,如int、double等。字符串是一种奇怪的类型,因为它被认为是值类型。
更复杂的类型(如数组和类)是引用类型。
当传递值类型(如in t和double)时,则传递值的副本,因此如果将int x=10传递到方法中,则在离开方法后,方法中对x的更改将不会反映出来。另一方面,如果传递myclass class1,对class1内属性的任何更改都将反映在函数外部。不要试图在方法中新建一个新的Class1,因为它不会在调用方外部发生变化。
如果要更改方法中的值类型,可以通过引用传递。如果要新建一个新的类或数组,则可以通过引用传递。
还有一件事:在使用out和ref之间并不是黑白相间的。只有当方法的设计总是只在方法内部创建类或数组时,才会使用out。如果希望允许创建新对象,可以在引用类型上使用ref。像,
1 2 3 4 5 6 7 8 9 10 11 12 | //function definition void foo (ref string[] temp) { if(temp == null) { temp = new string[] {"Hello World","You must be updated now" }; } else { // do something with the existing temp } } |
最后,如果这是您的实际代码:
1 2 3 4 5 | string[] temp = null; foo(ref temp); foreach (string s in temp) System.Console.WriteLine(s); |
后来:
1 2 3 4 5 | //function definition void foo (ref string[] temp) { temp = new string[] {"Hello World","You must be updated now" }; } |
那么,它实际上应该起作用。
将类引用视为"对象ID"。如果有一个类类型为
在特定情况下,所讨论的对象是一个数组。被调用的例程创建了一个全新的数组,但调用方以
以C作为参数传递的任何类型都是按值传递的。因此,如果这是一个字符串数组,c生成一个引用的副本(这是一个引用类型)并传递给它。但由于这是一个引用类型,两个变量(从内部方法范围和外部参数)都将指向托管堆(或大型对象堆)中的同一对象。
代码的问题在于您创建了一个新的变量(语法错误)。要解决这个问题,只需直接使用数组的索引器,即:
1 2 3 4 5 6 7 8 9 | void foo (string[] temp) // create a copy of a reference to the string array { temp[0] ="Boom"; // temp still points to the same object } ------------- string[] temp = new [] {"one","two","three"}; //outer variable foo(temp); // behind the scene we have two variables pointing to the same array foreach (string s in temp) System.Console.WriteLine(s); |
如果没有
这就是
但是,如果只希望方法更改引用的对象(取决于类型在构造后是否可以更改,即是否不可变),则不需要使用
因此,此方法将覆盖传递的数组的第一个元素:
1 2 3 4 5 6 7 8 9 10 11 12 | public static void foo(string[] ss) { if(ss!=null && ss.Length > 0) ss[0] ="Overwritten"; } public static void main() { var strings = new[] { String.Empty,"Original" }; foo(strings); Console.WriteLine(strings[0]); //will print 'Overwritten'. } |
但是,在上述代码中,
为此,我们需要传递一个对
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public static void foo(ref string[] ss) { if(ss==null || ss.Length == 0) ss= new string[1]; ss[0] ="Overwritten"; } public static void main() { string[] strings = null foo(ref strings); Console.WriteLine(strings[0]); //will still print 'Overwritten'. } |