'ref'和'out'关键字之间有什么区别?

TK.*_*TK. 851 c# reference ref out keyword

我正在创建一个函数,我需要传递一个对象,以便它可以被函数修改.有什么区别:

public void myFunction(ref MyClass someClass)
Run Code Online (Sandbox Code Playgroud)

public void myFunction(out MyClass someClass)
Run Code Online (Sandbox Code Playgroud)

我应该使用哪个以及为什么?

Run*_*tad 1120

ref告诉编译器在进入函数之前初始化对象,同时out告诉编译器该对象将在函数内初始化.

所以虽然ref是双向的,但是out只有出路.

  • 另外一个很酷的事情是函数必须分配给out参数.它不允许未分配. (260认同)
  • @faulty:违反直觉,引用类型总是按C#中的值传递,除非你使用ref说明符.如果设置myval = somenewval,则效果仅在该函数范围内.ref关键字允许您将myval更改为指向somenewval. (50认同)
  • @faulty:不,ref不仅适用于值类型.ref/out就像C/C++中的指针一样,它们处理对象的内存位置(间接在C#中)而不是直接对象. (17认同)
  • 'ref'只适用于价值类型?由于引用类型总是通过ref. (7认同)
  • 是.值类型包括结构 (3认同)
  • @DanielEarwicker:对于*用C#*实现的函数,编译器会坚持必须在所有非异常代码路径上写入`out`参数;然而,对于以其他语言实现的函数——包括*虚拟函数覆盖*——没有强制执行。例如,当找不到值时,`IDictionary<TK,TV>.TryGetValue(TK, out TV)` 的实现完全有可能不修改其 `out` 参数。 (2认同)

小智 511

ref修改意味着:

  1. 该值已设置
  2. 该方法可以读取和修改它.

out修改意味着:

  1. 该值未设置,并且在设置之前无法通过该方法读取.
  2. 该方法必须在返回之前设置它.

  • 这个答案最清楚,最简洁地解释了编译器在使用out关键字而不是ref关键字时所施加的限制. (30认同)
  • 来自MSDN:必须在使用前初始化ref参数,而在传递之前不必显式初始化out参数,并忽略任何先前的值. (5认同)
  • Panzercrisis,对于"out",被调用的方法可以读取它是否已经设置.但它必须再次设置它. (3认同)

Mic*_*urn 140

让我们说Dom在Peter的小隔间里出现关于TPS报告的备忘录.

如果Dom是一个参考论证,他会有一份备忘录的印刷版.

如果Dom是一个争吵,他会让彼得打印一份备忘录的新副本供他随身携带.

  • 参考Dom会用铅笔写报告,以便Peter可以修改它 (52认同)
  • 有趣而且有教育意义,stackoverflow需要更多这样的帖子 (18认同)
  • @Deebster你知道吗,那个比喻从来没有对你做过任何事,你为什么要这样折磨呢?;) (6认同)
  • 万一有人半信半疑地发现了这个答案,请观看电影“办公室空间”。 (2认同)
  • 一个很好的解释。这对于那些难以理解 C# 核心概念的学生来说非常有帮助。继续努力吧 :-) (2认同)

Jam*_*and 53

我将试着解释一下:

我想我们理解价值类型是如何运作的?值类型是(int,long,struct等).当您将它们发送到没有ref命令的函数时,它会复制数据.您在函数中对该数据执行的任何操作仅影响副本,而不影响原始副本.ref命令发送ACTUAL数据,任何更改都将影响函数外部的数据.

好的混淆部分,参考类型:

让我们创建一个引用类型:

List<string> someobject = new List<string>()
Run Code Online (Sandbox Code Playgroud)

当你新建某个对象时,会创建两个部分:

  1. 的存储器,其用于保存数据的块someobject.
  2. 该数据块的引用(指针).

现在,当你在发送someobject成方法,无需裁判它复制引用指针,而不是数据.所以你现在有这个:

(outside method) reference1 => someobject
(inside method)  reference2 => someobject
Run Code Online (Sandbox Code Playgroud)

两个引用指向同一个对象.如果使用reference2 修改someobject上的属性,它将影响reference1指向的相同数据.

 (inside method)  reference2.Add("SomeString");
 (outside method) reference1[0] == "SomeString"   //this is true
Run Code Online (Sandbox Code Playgroud)

如果将reference2归零或将其指向新数据,则不会影响reference1,也不会影响reference1指向的数据.

(inside method) reference2 = new List<string>();
(outside method) reference1 != null; reference1[0] == "SomeString" //this is true

The references are now pointing like this:
reference2 => new List<string>()
reference1 => someobject
Run Code Online (Sandbox Code Playgroud)

现在,当您通过引用方法发送someobject时会发生什么?在实际的参考,以someobject被发送到该方法.所以你现在只有一个数据引用:

(outside method) reference1 => someobject;
(inside method)  reference1 => someobject;
Run Code Online (Sandbox Code Playgroud)

但是,这是什么意思?除了两个主要内容之外,它与发送某些对象的行为完全相同:

1)当你在方法中取消引用时,它将使方法外的引用为空.

 (inside method)  reference1 = null;
 (outside method) reference1 == null;  //true
Run Code Online (Sandbox Code Playgroud)

2)您现在可以将引用指向完全不同的数据位置,并且函数外部的引用现在将指向新的数据位置.

 (inside method)  reference1 = new List<string>();
 (outside method) reference1.Count == 0; //this is true
Run Code Online (Sandbox Code Playgroud)

  • 赞成明确解释.但我认为这并没有回答这个问题,因为它没有解释`ref`和`out`参数之间的区别. (3认同)
  • 惊人的。您能解释一下与“out”关键字相同的内容吗? (2认同)

Rub*_*ink 28

裁判是在.

您应该out优先使用满足您要求的任何地方.


Naz*_*san 18

出:

在C#中,方法只能返回一个值.如果您想返回多个值,可以使用out关键字.out修饰符返回引用返回值.最简单的答案是关键字"out"用于从方法中获取值.

  1. 您无需初始化调用函数中的值.
  2. 您必须在被调用函数中分配值,否则编译器将报告错误.

参考:

在C#中,当您传递一个值类型(如int,float,double等)作为方法参数的参数时,它将按值传递.因此,如果修改参数值,则不会影响方法调用中的参数.但是如果用"ref"关键字标记参数,它将反映在实际变量中.

  1. 在调用函数之前,需要初始化变量.
  2. 在方法中为ref参数赋值不是必须的.如果您不更改值,需要将其标记为"ref"?

  • 在 c# 7 中,您可以使用 ValueTuples 返回多个值。 (3认同)

小智 13

扩展狗,猫的例子.使用ref的第二个方法更改调用者引用的对象.因此"猫"!!!

    public static void Foo()
    {
        MyClass myObject = new MyClass();
        myObject.Name = "Dog";
        Bar(myObject);
        Console.WriteLine(myObject.Name); // Writes "Dog". 
        Bar(ref myObject);
        Console.WriteLine(myObject.Name); // Writes "Cat". 
    }

    public static void Bar(MyClass someObject)
    {
        MyClass myTempObject = new MyClass();
        myTempObject.Name = "Cat";
        someObject = myTempObject;
    }

    public static void Bar(ref MyClass someObject)
    {
        MyClass myTempObject = new MyClass();
        myTempObject.Name = "Cat";
        someObject = myTempObject;
    }
Run Code Online (Sandbox Code Playgroud)


Alb*_*bic 8

由于您传入的是引用类型(类),因此无需使用,ref因为默认情况下,只传递对实际对象的引用,因此您始终更改引用后面的对象.

例:

public void Foo()
{
    MyClass myObject = new MyClass();
    myObject.Name = "Dog";
    Bar(myObject);
    Console.WriteLine(myObject.Name); // Writes "Cat".
}

public void Bar(MyClass someObject)
{
    someObject.Name = "Cat";
}
Run Code Online (Sandbox Code Playgroud)

只要您ref想要更改方法中的对象,只要传入一个类就不必使用.

  • 这是错误的 - 尝试以下方法:将`someObject = null`添加到`Bar`结束执行.您的代码运行正常,因为只有`Bar`对该实例的引用被清零.现在将`Bar`更改为`Bar(ref MyClass someObject)`并再次执行 - 你将得到一个`NullReferenceException`,因为`Foo`对该实例的引用也已被置零. (7认同)
  • 仅当没有创建和返回新对象时,此方法才有效.创建新对象时,对旧对象的引用将丢失. (5认同)

gma*_*ser 8

ref并且out表现相似,除了以下差异.

  • ref变量必须在使用前初始化.out变量可以不经赋值使用
  • out必须将参数视为使用它的函数的未分配值.因此,我们可以out在调用代码中使用初始化参数,但是当函数执行时该值将丢失.


2up*_*dia 7

对于那些通过实例学习的人(像我一样),这就是Anthony Kolesov所说的.

我已经创建了一些ref,out和其他的最小例子来说明这一点.我没有介绍最佳实践,仅仅是了解差异的例子.

https://gist.github.com/2upmedia/6d98a57b68d849ee7091


mmm*_*mmm 6

"贝克"

那是因为第一个将你的字符串引用更改为指向"Baker".可以更改引用,因为您通过ref关键字(=>对字符串引用的引用)传递了引用.第二个调用获取对字符串的引用的副本.

字符串起初看起来有些特殊.但是string只是一个引用类,如果你定义的话

string s = "Able";
Run Code Online (Sandbox Code Playgroud)

然后s是对包含文本"Able"的字符串类的引用!通过相同变量的另一个赋值

s = "Baker";
Run Code Online (Sandbox Code Playgroud)

不会更改原始字符串,只是创建一个新实例,让我们指向该实例!

您可以使用以下小代码示例尝试:

string s = "Able";
string s2 = s;
s = "Baker";
Console.WriteLine(s2);
Run Code Online (Sandbox Code Playgroud)

你能指望什么?你将获得的仍然是"Able",因为你只是将s中的引用设置为另一个实例,而s2指向原始实例.

编辑:字符串也是不可变的,这意味着根本没有修改现有字符串实例的方法或属性(你可以尝试在文档中找到一个,但你不会吝啬任何:-)).所有字符串操作方法都返回一个新的字符串实 (这就是为什么在使用StringBuilder类时经常获得更好的性能)


Fai*_*eer 5

Out: return语句只能用于从函数中返回一个值。但是,使用输出参数,您可以从函数返回两个值。输出参数类似于参考参数,不同之处在于它们将数据从方法中传输出来而不是传输到方法中。

以下示例说明了这一点:

using System;

namespace CalculatorApplication
{
   class NumberManipulator
   {
      public void getValue(out int x )
      {
         int temp = 5;
         x = temp;
      }

      static void Main(string[] args)
      {
         NumberManipulator n = new NumberManipulator();
         /* local variable definition */
         int a = 100;

         Console.WriteLine("Before method call, value of a : {0}", a);

         /* calling a function to get the value */
         n.getValue(out a);

         Console.WriteLine("After method call, value of a : {0}", a);
         Console.ReadLine();

      }
   }
}
Run Code Online (Sandbox Code Playgroud)

ref: 引用参数是对变量的存储位置的引用。当您通过引用传递参数时,与值参数不同,不会为这些参数创建新的存储位置。参考参数表示与提供给该方法的实际参数相同的存储位置。

在C#中,使用ref关键字声明参考参数。下面的示例演示了这一点:

using System;
namespace CalculatorApplication
{
   class NumberManipulator
   {
      public void swap(ref int x, ref int y)
      {
         int temp;

         temp = x; /* save the value of x */
         x = y;   /* put y into x */
         y = temp; /* put temp into y */
       }

      static void Main(string[] args)
      {
         NumberManipulator n = new NumberManipulator();
         /* local variable definition */
         int a = 100;
         int b = 200;

         Console.WriteLine("Before swap, value of a : {0}", a);
         Console.WriteLine("Before swap, value of b : {0}", b);

         /* calling a function to swap the values */
         n.swap(ref a, ref b);

         Console.WriteLine("After swap, value of a : {0}", a);
         Console.WriteLine("After swap, value of b : {0}", b);

         Console.ReadLine();

      }
   }
}
Run Code Online (Sandbox Code Playgroud)


Far*_* S. 5

ref表示ref参数中的值已设置,该方法可以读取和修改它。使用ref关键字与说调用方负责初始化参数的值相同。


out告诉编译器,对象的初始化是函数的责任,该函数必须分配给out参数。不允许将其保留为未分配状态。


Cai*_*ard 5

除了允许您将其他人的变量重新分配给类的不同实例、返回多个值等之外,还可以使用refout让其他人知道您需要他们提供什么以及您打算如何处理他们提供的变量

  • 不需要 ref,或者out如果您要做的只是修改参数中传递的实例内内容。MyClasssomeClass

    • 调用方法将看到变化,例如someClass.Message = "Hello World"您是否使用ref,out或什么都不使用
    • 写入someClass = new MyClass()内部会交换出仅在方法范围内myFunction(someClass)看到的对象。调用方法仍然知道它创建并传递给您的方法的原始实例someClassmyFunctionMyClass
  • 需要 ref或者out如果您计划将其替换someClass为一个全新的对象并希望调用方法看到您的更改

    • someClass = new MyClass()在内部写入myFunction(out someClass)会更改调用方法所看到的对象myFunction

存在其他程序员

他们想知道你将如何处理他们的数据。想象一下您正在编写一个将被数百万开发人员使用的库。您希望他们知道当他们调用您的方法时您将如何处理他们的变量

  • 使用ref声明“当您调用我的方法时,传递一个分配给某个值的变量。请注意,我可能会在我的方法过程中将其完全更改为其他内容。不要期望您的变量指向旧对象当我完成时”

  • usingout声明了“向我的方法传递一个占位符变量。它是否有值并不重要;编译器会强制我将它赋给一个新值。我绝对保证你所指向的对象在调用我的方法之前的变量,在我完成时会有所不同

顺便说一句,在 C#7.2 中也有一个in修饰符

这可以防止该方法将传入的实例交换为不同的实例。可以将其视为对数百万开发人员说“将您的原始变量引用传递给我,我保证不会将您精心制作的数据交换为其他内容”。in有一些特殊性,在某些情况下,例如可能需要隐式转换以使您的short与int兼容,in int编译器将暂时创建一个int,将您的short扩展到它,通过引用传递它并完成。它可以做到这一点,因为你已经声明你不会搞乱它。


微软.TryParse通过数字类型的方法做到了这一点:

int i = 98234957;
bool success = int.TryParse("123", out i);
Run Code Online (Sandbox Code Playgroud)

通过标记参数,因为out他们在这里主动声明“我们肯定会将您精心设计的值 98234957 更改为其他值”

当然,对于解析值类型之类的事情,他们有点必须这样做,因为如果不允许解析方法将值类型交换为其他内容,那么它就不会很好地工作。但是想象一下,在某些情况下有一些虚构的方法您正在创建的库:

public void PoorlyNamedMethod(out SomeClass x)
Run Code Online (Sandbox Code Playgroud)

你可以看到它是一个out,因此你可以知道,如果你花几个小时处理数字,创建完美的 SomeClass:

SomeClass x = SpendHoursMakingMeAPerfectSomeClass();
//now give it to the library
PoorlyNamedMethod(out x);
Run Code Online (Sandbox Code Playgroud)

好吧,那是浪费时间,花那么多时间来上完美的课。它肯定会被扔掉并被 PoorlyNamedMethod 取代


snr*_*snr 5

对于那些寻找简洁答案的人。

这两个refout关键字用于通过逐reference


ref关键字变量必须有一个值或必须引用一个对象或null 它传递之前


与 不同refout关键字的变量必须有值或必须引用一个对象或null 它传递之后,并且传递之前不需要有值或引用一个对象。