关于c#4.0:提及C#函数参数中的ref

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所提到的,如果您正在创建一个全新的对象,那么您应该使用一个out来表示这一点。ref通常更多用于值对象,而不是通过引用传递。

总结:

  • 如果它是一个值类型,并且您希望更新该值并使其反映到任何地方,那么您需要使用ref
  • 如果它是引用类型
    • 如果您希望更新该值以使其反映到所有地方,那么您可以正常地传递它(没有refout)
    • 如果您希望在方法内创建一个全新的实例并将其反映出来,那么应该使用out

更新

这是一篇详细解释您所要求的内容的msdn文章:)


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

下面是一个示例,说明outref和常规传递如何在数组arg上工作。如您所见,无论是否指定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
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始终按值传递参数,除非使用refout修饰符。

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"。如果有一个类类型为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 typecarwill simply want the called code to do something with thecaridentified by that variable. In a few cases, however, one may be necessary to let the called code change the object ID stored withinmycaritself, so that it points to an entirely different instance ofcar`。

在特定情况下,所讨论的对象是一个数组。被调用的例程创建了一个全新的数组,但调用方以temp等于空开始。调用者看到新数组的唯一方法是将对它的引用存储到temp中。否则,调用方将继续查看之前的数组temp(或null),如果没有存储数组引用。


以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);


如果没有ref,对数组的引用将被简单地复制并传递给方法(按值-因为引用类型的值是对对象的引用);允许该方法访问同一数组,但不允许它修改调用方自己的引用。

这就是ref关键字(实际上是out)的作用。

但是,如果只希望方法更改引用的对象(取决于类型在构造后是否可以更改,即是否不可变),则不需要使用ref

因此,此方法将覆盖传递的数组的第一个元素:

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

但是,在上述代码中,foo不能做的是newss参数,并期望改变作为main参数传递的引用strings的值。

为此,我们需要传递一个对stringslocal的引用,这就是ref的来源。如果我们这样做-那么,如果传递空值,可以更改main()中的strings,以指向新数组:

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