提到C#函数参数中的ref

时间:2012-03-19 16:33:55

标签: c# c#-4.0

我创建了一个函数,我在其中传递字符串数组,如string [] aa = null
在函数定义中,我在其中进行了一些更新,然后我发现在执行/返回函数时它的值没有更新。我无法理解C#中哪些内容需要提及ref关键字。是不是总是通过引用传递参数为什么需要提一下ref?哪些是需要用ref提及以便通过引用传递的对象?

代码是这样的,只是试图展示我在做什么,我无法在不使用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)

7 个答案:

答案 0 :(得分:4)

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

Here is a good article that explains this pretty well

而且,当我说你按价值传递参考时,这是解释我的意思的片段

  

在C#中,参数是(默认情况下)按值传递,这意味着   传递给方法时会隐式复制它们。对于价值类型   参数,这意味着物理复制实例(在相同的   方式p2被复制了),而对于参考类型,它意味着复制a   参考(以同样的方式复制f2)

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

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

总结:

  • 如果它是值类型,并且您希望更新该值并使其反映在任何地方,那么您需要使用ref
  • 如果是参考类型
    • 如果您希望更新价值以便它反映在任何地方,那么您可以正常传递(不是refout
    • 如果您希望在方法中创建一个全新的实例并反映出来,那么您应该使用out

<强>更新

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

答案 1 :(得分:2)

除非您使用refout修饰符,否则C#始终按值传递参数

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

答案 2 :(得分:2)

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

以下是一个示例,说明outref和常规传递如何在数组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";
    }
}

答案 3 :(得分: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无法做到的是new ss参数,并希望更改引用的 {{ 1}}作为strings的参数传递。

要做,我们需要将引用传递给main本地,这是strings的来源。如果我们这样做 - 那么refstrings中,如果传递为null,则可以更改为指向新数组:

main()

答案 4 :(得分: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);

答案 5 :(得分:1)

将类引用视为“对象ID”。如果一个类型为MyCar的变量Car,则该变量实际上并不包含汽车。相反,它保存实际存储在其他地方的汽车的“对象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 {{1 }} Car`。

在您的特定情况下,有问题的对象是一个数组。被调用的例程创建一个全新的数组,但调用者以itself, so that it points to an entirely different instance of等于null开始。调用者要查看新数组的唯一方法是将对它的引用存储到temp中。否则调用者将继续查看temp之前的任何数组(或temp,如果没有存储数组引用的话)。

答案 6 :(得分:1)

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

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

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

当您传递诸如整数和双精度之类的值类型时,您传递的是值的副本,因此如果将int x = 10传递给方法,则一旦离开该方法,将不会反映方法中对x的更改。另一方面,如果您传递MyClass class1,对 class1中属性的任何更改都将反映在函数外部。只是不要尝试在方法中新建一个新的class1,因为这不会在调用者之外发生变化。

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

还有一件事:使用out和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" };
    } 

然后,它应该实际工作。