2012-03-19 51 views
1

我做了一个函数,我想在其中传递字符串数组,像string [] aa = null
在函数定义中我做了一些更新,然后我发现它的值是当函数执行/返回时没有更新。我无法理解C#中哪些事情需要提及ref关键字。它不是总是通过参考传递参数为什么需要提及ref?哪些是需要与ref一起提及才能通过引用传递的对象?在C#函数参数中提到的参考

代码是这样的,只是试图显示我在做什么,我无法更新值,而不使用ref。

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) 
+0

这样的社区,我阅读所有的答案,并学到很多 – 2012-03-19 17:34:02

+0

那么,为什么你的代码没有工作? – 2012-03-19 17:41:44

+0

我的代码有效,我需要理解这个概念,我是C++开发人员,通过学习它的基本概念来学习新的语言。 – 2012-03-19 17:50:56

回答

1

首先,你的伪代码应该工作。但是,在我们开始之前,这里有三件事:值类型,引用类型和“ref”关键字。

值类型通常是你简单的基本类型,如int,double等。字符串是一个奇怪的类型,因为它被认为是一个值类型。

更复杂的类型,如数组和类是引用类型。

当您传递值类型如int和double时,则传递值的副本,所以如果将int x = 10传递给方法,则在离开方法后,方法中的x更改将不会反映出来。另一方面,如果您通过MyClass class1,则属性 class1中的任何更改都将反映在该函数之外。只是不要尝试在你的方法中新建一个新的class1,因为这在调用者之外不会改变。

如果您想更改方法中的值类型,请通过ref。如果你想新建一个新的类或数组,那么你通过ref。

还有一件事:它不是使用之间与参考之间的黑白。只有当方法的设计始终只在方法内创建类或数组时,才会使用它。如果你想允许创建一个新的对象,你可以在引用类型上使用ref。像,

//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 
     } 
    } 

最后,如果这是你的实际代码:

 string[] temp = null; 
     foo(ref temp); 

     foreach (string s in temp) 
      System.Console.WriteLine(s); 

后来:

//function definition 
    void foo (ref string[] temp) 
    { 
     temp = new string[] { "Hello World ", "You must be updated now" }; 
    } 

然后,它实际上应该工作。

+0

谢谢,SO社区真棒 – 2012-03-19 17:29:22

+0

值类型通常是你简单的基本类型,如int ,double等等。字符串是一个奇怪的字符串,因为它被认为是一个值类型。 更复杂的类型,如数组和类是引用类型。 – 2012-03-19 17:35:49

+0

非常好的一点 – 2012-03-19 17:36:00

4

你有点误会错综复杂。如果你传递一个引用对象,那么它会通过值传递引用(继续阅读并希望它应该开始变得更有意义)。但是,这并不意味着它是通过ref。事实上,默认情况下,C#参数是按值传递的。

Here is a good article that explains this pretty well

而且,这是解释我的意思,当我说,你是按值

通过参考在C#中的片段,参数是价值,意义传递(默认)那 它们在传递给方法时被隐式复制。对于值型 参数,这意味着在物理复制实例(在相同 方式P2被复制),而对于引用类型它意味着复制 参考(以相同的方式F2被复制)

。然而,在你的情况下,你传递你的对象,然后在你的方法中创建一个带有新引用的新对象。因此,原始引用保持不变,您可以通过执行更新而不是全新的创建来看到这一点。当你明确地说ref的时候,你现在传递了对引用的引用,所以它可以工作,因为你只使用指向引用的指针,并且当你创建新对象时,它将被放置到该引用位置。

正如eouw0o83hf提到的,如果你正在创建一个全新的对象,那么你应该使用out来表示这个。 ref通常更多用于值对象,它们不通过引用传递。

总结:

  • 如果是值类型,以及要更新的价值,有它处处体现,那么你需要使用ref
  • 如果是引用类型
    • 如果您希望更新的值,以便它处处体现,那么您可以在正常通过它(无refout
    • 如果你想在方法内部创建一个全新的实例,有反映,那么你应该使用out

UPDATE

Here is an MSDN article explaining exactly what you are asking :)

+0

是的,请检查,更新它在功能或通过引用它必须要求ref关键字,我也希望没有办法使用ref前缀通过引用传递的场景 – 2012-03-19 16:39:28

+1

我已经在验证后更新了我的描述。如果你有一个C++或其他语言背景的指针,那么这将变得很明显,否则希望我解释得足够好:) – 2012-03-19 16:56:03

+0

@JustinPihony,你的第一段有点矛盾,因为上半段说它不会没有工作(“原始参考保持不变”,但没有),但第二个说它确实有效。等等......让我试试我的答案...... – 2012-03-19 17:06:29

2

C#总是价值传递参数,除非你使用refout修饰。

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" } 
} 
+0

“C#总是按值传递参数” - 我认为这个语句有点乱码,因为引用类型的对象是通过引用传递的。 'MutateByRef'也可以不用'ref'修饰符。 – eouw0o83hf 2012-03-19 17:30:15

+0

谢谢,SO社区真棒 – 2012-03-19 17:31:08

+0

@ eouw0o83hf:我不同意。什么是乱码*“C#总是按值传递参数,除非使用ref或out修饰符”*?引用类型的参数是按值传递的,只不过这些值恰好是这些情况下的引用(参见上面的'MutateByVal')。 – LukeH 2012-03-19 18:06:58

2

这是因为你实际上是返回一个全新的对象而不是修改现有条目。如果您正在分配新对象,则应该使用out

下面是一个示例,显示out,ref和常规传递如何在数组arg上工作。正如你所看到的那样,无论指定了ref,数组都是通过引用传递的;但是,如果返回一个全新的对象,你需要指定out

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"; 
    } 
} 
+0

这是C#使用传递引用的方式吗?是否有可能做到这一点没有任何参考或关键字? – 2012-03-19 16:43:44

+0

是的,这是参考传递的工作原理。如果您想修改现有数组的成员,则不需要指定'ref'。如果你返回一个全新的对象,你需要指定'out'。 – eouw0o83hf 2012-03-19 16:48:00

+0

谢谢,SO社区很棒 – 2012-03-19 17:31:32

1

没有ref参考阵列被简单地复制并传递给方法(按价值 - 因为引用类型的是对象的引用);允许该方法访问相同的数组,但它确实允许它修改调用者自己的引用。

这就是ref(或确实是out)关键字的用途。

但是 - 你并不需要使用ref,如果你只是想的方法来改变所引用(取决于类型可以建成后改变 - 即,如果它是不可变与否)的对象。

那么这种方法将覆盖传递的数组的第一个元素:

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'. 
} 

foo不能在上面的代码做什么,但是,newss参数,并期望改变的的值参考文献strings作为参数从main传递。

为此,我们需要传递一个参考strings地方,这就是ref进来。如果我们做到这一点 - 然后在main()strings可以改变,如果传递空,指向一个新的阵列:

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'. 
} 
+0

我只是意识到我实际上已经忘记在我的第二个代码块中放入'ref'!但是,我今天起床了十二个小时,为其中的十一个人(还有另外五个小时睡觉前)工作,这是我的借口。:D – 2012-03-19 17:16:47

+0

赛车家伙的借口,只是开个玩笑:D – 2012-03-19 17:26:25

+0

谢谢,SO社区真棒 – 2012-03-19 17:28:58

1

任何类型的在C#作为参数传递由值传递。因此,如果这是一个字符串数组,C#将复制一个引用(这是一个引用类型)并将其传入。但由于这是一个引用类型,因此这两个变量(来自内部方法范围和外部参数)都会指向同一对象位于托管堆(或大型对象堆)中。

你的代码的问题是你创建一个新的变量(语法不正确)。为了解决这个问题你只需要使用直接的阵列的索引,即:

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); 
+0

谢谢,SO社区真棒 – 2012-03-19 17:29:04

1

思考的类引用为“对象ID”。如果有一个类型为Car的变量MyCar,则该变量实际上不包含汽车。相反,它保存实际存储在别处的汽车的“对象ID”。如果MyCar持有“#1543”,则说明MyCar.Color = CarColors.Purple;实际上不会修改变量MyCar。相反,它会告诉系统“对象#1543应该是一个Car。设置其Color属性CarColors.Purple." In many cases, a routine which passes a variable of type汽车will simply want the called code to do something with the汽车identified by that variable. In a few cases, however, one may be necessary to let the called code change the object ID stored within MyCar itself, so that it points to an entirely different instance of Car`。

你的具体情况,所涉及的对象是一个数组,被叫例程创建一个全新的数组,但调用者以temp等于null开头,调用者看到新数组的唯一方法是如果对它的引用存储到temp中否则调用者将继续查看任何数组(或null,如果没有存储阵列参考)

+0

谢谢,SO社区真棒 – 2012-03-19 17:29:13