关于C#:ref和out参数问题

Ref and Out Parameters Questions

本问题已经有最佳答案,请猛点这里访问。

我原以为我明白其中的区别,但现在我不太确定。我已经看了几遍技术答案,但我不明白发生了什么。我有这个例子。

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
class Program
   {
      static void Main()
      {
         int val = 0;

         Example1(val);
         Console.WriteLine(val); // Still 0!

         Example2(ref val);
         Console.WriteLine(val); // Now 2!

         Example3(out val);
         Console.WriteLine(val); // Now 3!
      }

      static void Example1(int value)
         {
             value = 1;
         }

      static void Example2(ref int value)
         {
             value = 2;
         }

      static void Example3(out int value)
         {
           value = 3;
         }
    }

我一直认为默认参数之间的区别是,如果我将val传递到example1中,就不能使用赋值。

但是使用ref关键字val仍然是0,但是我已经创建了一个引用,它现在在example2(ref val)中被视为变量"value"。到目前为止我有什么事要挂断吗?如果我曾经用过

1
2
3
int value = 0;
Example1(value);
Console.WriteLine(value); // this would then return 1 correct?

现在,out关键字发生了什么?和裁判一样?


好的,这是评论中链接问题的副本,但我会尽力为您解释。

未修饰参数

案例A:public void MyFunc(int x) { }

-或

案例B:public void MyFunc(MyClass y) { }

在案例A中,参数是值类型,默认情况下,值类型作为原始值的副本传递给函数。这并不意味着不允许您修改该值,但该值不会在调用位置反射回来。这对于所有值类型都是相同的,值在传递给函数之前会被复制。

在案例B中,参数是引用类型。这些类型在默认情况下按原样传递。它们不会被复制,但不允许您更改引用(通过将newnull值指定给类型)。您可以更改对象的内容(其中的任何属性/字段),它将反映回调用位置。

REF关键字

案例A:public void MyFunc(ref int x) { }

-或

案例B:public void MyFunc(ref MyClass x) { }

在案例A中,您告诉编译器您希望通过引用传递值,这意味着编译器不会复制类型,而是传递对该类型的引用。允许函数更改它。

在案例B中,您告诉编译器允许函数更改引用指向的位置(您可以创建一个new或将其设置为null,它将反映在调用站点中。

out关键字

案例A:public void MyFunc(out int x) { }

-或

案例B:public void MyFunc(out MyClass x) { }

这里,您基本上是为函数定义额外的返回类型。它是一个方法参数,告诉调用者期望结果出现在x变量的位置。两者都是一样的。在任何情况下,调用者都不应期望x之前的任何值在之后都是相同的。实际上,您可以期望它不会是相同的,因为在允许X返回之前,该方法需要将一个新值赋给X。

out基本上意味着您为返回值提供了一个位置,对于值类型,只使用默认的构造函数,对于引用类型,在传入值之前先将值初始化为null


看看这是否有帮助:

没有"装饰家"

1
2
3
4
5
6
7
8
9
10
11
12
static void Example(int value)
{
    Console.WriteLine("Value is {0}", value);
    value = 99;
}

usage
{
    int value = 10;
    Example(notADefaultValue); // Will print <Value is 10>
    Console.WriteLine("Value is {0}", value); // will print <Value is 10>
}

摘要:默认情况下,值类型(structs/enums/int/double/etc)不会作为引用传入,因此对方法中的变量所做的任何操作都不会影响调用方。因此,"ref"关键字。如果您将一个引用类型传递给一个方法(即一个类),并改变它的内部结构,这些将影响调用者。

默认参数:

1
2
3
4
5
6
7
8
9
10
11
static void Example(int value = 5)
{
    Console.WriteLine("Value is {0}", value);
}

usage
{
    int notADefaultValue = 10;
    Example(notADefaultValue); // Will print <Value is 10>
    Example(); // will print <Value is 5>
}

摘要:默认值允许您在不显式地需要传递参数的情况下调用方法,使用默认值。

参考参数:

1
2
3
4
5
6
7
8
9
10
11
12
static void Example(ref int value)
{
    Console.WriteLine("Value is {0}", value);
    value = 99;
}

usage
{
    int value = 10;
    Example(ref value); // Will print <Value is 10>
    Console.WriteLine("Value is {0}", value); // Will print <Value is 99>
}

摘要:如果将值类型作为引用(reference)传入,则值本身会发生更改。默认情况下,所有引用类型都作为引用传入。仅供参考,int是一个基元类型,因此需要一个显式的"ref"。

输出参数:

1
2
3
4
5
6
7
8
9
10
11
12
static void Example(out int value)
{
    value = 99;
    Console.WriteLine("Value is {0}", value);
}

usage
{
    int value; // no need to init it
    Example(out value); // Will print <Value is 99>
    Console.WriteLine("Value is {0}", value); // Will print <Value is 99>
}

摘要:out参数类似于返回变量,但通过方法的签名传入。最常见的例子是typarse,其中方法返回重要信息,根据该信息,out参数是有效的还是无效的(如果为真,则有效)。


在第一个示例中,您没有指定变量是引用,因为它是基本类型,所以它只复制提供的数字。因此,Example1中的valueMainvalue的副本。

第二个示例使用引用。这意味着函数Example2和函数Main的内部都是指内存中的同一个位置,因此在进入和退出函数时,值被传递。

在第三个示例中,out关键字与第二个关键字的作用相同,只是在输入函数时被初始化为0。因此,它只是返回某种数据的参数。该值在退出函数时才被传递。