【问题标题】:What's the difference between the 'ref' and 'out' keywords?'ref' 和 'out' 关键字有什么区别?
【发布时间】:2010-09-28 03:46:14
【问题描述】:

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

public void myFunction(ref MyClass someClass)

public void myFunction(out MyClass someClass)

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

【问题讨论】:

  • 你:我需要传递一个对象以便它可以被修改看起来MyClass将是一个class类型,即引用类型。在这种情况下,即使没有 ref/out 关键字,您传递的对象也可以被 myFunction 修改。 myFunction 将收到一个指向 same 对象的 new 引用,并且它可以根据需要修改同一个对象。 ref 关键字的不同之处在于,myFunction 收到了对同一对象的 same 引用。仅当 myFunction 将引用更改为指向 另一个 对象时,这才是重要的。
  • 当@AnthonyKolesov 的答案非常完美时,我对这里的大量令人困惑的答案感到困惑。
  • 当您希望方法返回多个值时,声明一个 out 方法很有用。可以将一个参数分配给 null。这使方法能够有选择地返回值。
  • 这里用Example解释一下更容易理解:)dotnet-tricks.com/Tutorial/csharp/…
  • @JeppeStigNielsen 的评论在技术上是对 OP 实际问题的(唯一)正确答案。要将对象传递给方法以便方法可以修改对象,只需将(对对象的引用)按值传递给方法。通过对象参数更改方法中的对象修改原始对象,即使该方法包含其自己的单独变量(引用同一对象)。

标签: c# reference keyword out ref


【解决方案1】:

ref 告诉编译器对象在进入函数之前被初始化,而out 告诉编译器对象将在函数内部被初始化。

所以ref 是双向的,out 是唯一的。

【讨论】:

  • out 的另一件很酷的事情是该函数必须分配给 out 参数。不允许不分配。
  • 'ref' 是否仅适用于值类型?由于引用类型总是通过 ref 传递。
  • 是的。值类型,包括结构
  • @faulty:不,ref 不仅适用于值类型。 ref/out 就像 C/C++ 中的指针,它们处理对象的内存位置(在 C# 中间接)而不是直接对象。
  • @faulty:与直觉相反,引用类型在 C# 中总是按值传递,除非您使用 ref 说明符。如果设置 myval=somenewval,则效果仅在该函数范围内。 ref 关键字将允许您将 myval 更改为指向 somenewval。
【解决方案2】:

ref 修饰符意味着:

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

out 修饰符意味着:

  1. 值未设置,无法通过方法读取直到设置。
  2. 方法必须在返回之前设置它。

【讨论】:

  • 这个答案最清楚简洁地解释了编译器在使用 out 关键字而不是 ref 关键字时施加的限制。
  • 来自 MSDN:ref 参数必须在使用前初始化,而 out 参数在传递之前不必显式初始化,并且忽略任何先前的值。
  • 使用out,如果它在方法被调用之前已经被初始化,是否可以在方法中读取它,在它被该方法设置之前?我的意思是,被调用方法可以读取调用方法作为参数传递给它的内容吗?
  • Panzercrisis,对于“out”,如果已经设置,调用的方法可以读取。但它必须重新设置。
【解决方案3】:

假设 Dom 出现在 Peter 的隔间,内容是关于 TPS 报告的备忘录。

如果 Dom 是一个 ref 参数,他将拥有一份备忘录的打印副本。

如果 Dom 是一个公开的论点,他会让 Peter 打印一份新的备忘录让他随身携带。

【讨论】:

  • ref Dom 会用铅笔写报告,以便彼得可以修改它
  • @Deebster 你知道,那个比喻对你没有任何作用,你为什么要这样折磨它? ;)
  • 既有趣又有教育意义,stackoverflow 需要更多这样的帖子
  • 万一有人觉得这个答案只有一半好笑,请观看电影“办公空间”。
  • 一个很好的解释。这对于难以理解 C# 核心概念的学生非常有帮助。继续努力:-)
【解决方案4】:

我会试着解释一下:

我想我们理解值类型是如何工作的吧?值类型是(int、long、struct 等)。当您将它们发送到没有 ref 命令的函数时,它会复制 数据。您对函数中的数据所做的任何事情都只会影响副本,而不是原始数据。 ref 命令发送 ACTUAL 数据,任何更改都会影响函数外的数据。

好的,令人困惑的部分,引用类型:

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

List<string> someobject = new List<string>()

当你新建 someobject 时,会创建两个部分:

  1. someobject 保存数据的内存块。
  2. 对该块的引用(指针) 数据。

现在,当您将 someobject 发送到没有 ref 的方法时,它会复制 reference 指针,而不是数据。所以你现在有了这个:

(outside method) reference1 => someobject
(inside method)  reference2 => someobject

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

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

如果您将 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

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

(outside method) reference1 => someobject;
(inside method)  reference1 => someobject;

但这意味着什么?它的作用与不通过 ref 发送 someobject 完全相同,除了两个主要的事情:

1) 当您将方法内的引用清空时,它会将方法外的引用清空。

 (inside method)  reference1 = null;
 (outside method) reference1 == null;  //true

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

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

【讨论】:

  • 你的意思毕竟(在参考案例中)只有一个对数据的引用,但它有两个别名。对吗?
  • 赞成明确的解释。但我认为这并不能回答问题,因为它不能解释 refout 参数之间的区别。
  • 太棒了。你能解释一下out关键字吗?
【解决方案5】:

ref is in and out.

您应该优先使用out,只要它能够满足您的要求。

【讨论】:

  • 不完全是,作为接受的答案参考,如果定向和无用忽略值类型,如果没有传回。
  • @kenny:你能澄清一下吗?也就是说,你会改变哪些词来保持答案的精神,但消除你所感知的不准确之处?我的回答不是新手的疯狂猜测,但您评论中的匆忙(简洁,错别字)似乎假设它是。目的是提供一种用最少的词来思考差异的方法。
  • (顺便说一句,我熟悉值类型、引用类型、按引用传递、按值传递、COM 和 C++,如果您发现在您的说明中引用这些概念很有用)
  • 对象引用按值传递(使用“ref”或“out”关键字时除外)。将对象视为 ID 号。如果一个类变量包含“Object #1943”,并且将该变量按值传递给例程,则该例程可以对 Object #1943 进行更改,但它不能使变量指向“Object #1943”以外的任何内容。如果变量是通过引用传递的,例程可以使变量点保持“对象#5441”。
  • @supercat:我很喜欢你对 ref vs val 的解释(以及这个后续的类比)。我认为肯尼实际上不需要向他解释任何这些,(相对)像他的 cmets 一样令人困惑。我真希望我们都可以删除这些该死的 cmets,因为它们只是让每个人都感到困惑。所有这些废话的根本原因似乎是肯尼误读了我的答案,并且还没有指出应该添加/删除/替换的单个单词。我们三个人都没有从我们不知道的讨论中学到任何东西,而另一个答案的赞成票数量可笑。
【解决方案6】:

输出:

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

  1. 您不需要在调用函数中初始化值。
  2. 必须在被调用函数中赋值,否则编译器会报错。

参考:

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

  1. 您需要在调用函数之前初始化变量。
  2. 在方法中为 ref 参数分配任何值不是强制性的。如果不改变值,有什么必要将其标记为“ref”?

【讨论】:

  • "在 C# 中,一个方法只能返回一个值。如果你想返回多个值,可以使用 out 关键字。"我们也可以使用“ref”来返回值。那么如果我们想从一个方法返回多个值,我们可以同时使用 ref 和 out 吗?
  • 在 c# 7 中,您可以使用 ValueTuples 返回多个值。
【解决方案7】:

扩展狗,猫的例子。带有 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;
    }

【讨论】:

    【解决方案8】:

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


    out 告诉编译器对象的初始化是由 该函数,该函数必须分配给 out 参数。 不允许不分配。

    https://www.codemaggot.com/ref-and-out-keywords/

    【讨论】:

      【解决方案9】:

      refout 行为相似,但有以下区别。

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

      【讨论】:

        【解决方案10】:

        对于那些通过示例学习的人(比如我),这里是Anthony Kolesov is saying

        我创建了一些 ref、out 和其他的最小示例来说明这一点。我不是在介绍最佳实践,只是为了理解差异而举例说明。

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

        【讨论】:

          【解决方案11】:

          由于您传递的是引用类型(类),因此无需使用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";
          }
          

          只要你传入一个类,如果你想改变你的方法中的对象,你就不必使用ref

          【讨论】:

          • 只有在没有创建和返回新对象时才有效。创建新对象时,对旧对象的引用将丢失。
          • 这是错误的 - 请尝试以下操作:将 someObject = null 添加到 Bar 结束执行。您的代码将运行良好,因为只有 Bar 对实例的引用被取消。现在将 Bar 更改为 Bar(ref MyClass someObject) 并再次执行 - 您将获得 NullReferenceException,因为 Foo 对实例的引用也已被取消。
          【解决方案12】:

          “面包师”

          这是因为第一个将您的字符串引用更改为指向“Baker”。更改引用是可能的,因为您通过 ref 关键字(=> 对字符串引用的引用)传递了它。 第二次调用获取对字符串的引用的副本。

          string 一开始看起来有点特别。但是字符串只是一个参考类,如果你定义了

          string s = "Able";
          

          那么 s 是对包含文本“Able”的字符串类的引用! 通过

          对同一个变量进行另一个赋值
          s = "Baker";
          

          不改变原始字符串,只是创建一个新实例,让 s 指向那个实例!

          您可以通过以下小代码示例进行尝试:

          string s = "Able";
          string s2 = s;
          s = "Baker";
          Console.WriteLine(s2);
          

          你期待什么? 您将得到的仍然是“Able”,因为您只是将 s 中的引用设置为另一个实例,而 s2 指向原始实例。

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

          【讨论】:

          • 没错。因此,严格地说“既然你传入了一个引用类型(一个类),就不需要使用 ref”。
          • 理论上这样说是对的,因为他写了“以便可以修改”,这在字符串上是不可能的。但是由于不可变对象,“ref”和“out”对于引用类型也非常有用! (.Net 包含很多不可变的类!)
          • 是的,你是对的。我没有想到像字符串这样的不可变对象,因为大多数对象都是可变的。
          • 嗯,这是一个在 LQP 中看到的令人费解的答案,可以肯定的是;它没有任何问题,只是它似乎是对另一条评论的冗长而彻底的回应(因为原始问题在其修订中都没有提到 Able 和 Baker),就好像这是一个论坛。我想这在很久以前还没有真正解决。
          【解决方案13】:

          适合那些寻求简洁答案的人。

          refout 关键字都用于传递-reference


          ref关键字的变量必须有值或者必须引用一个对象 或null它通过之前。


          ref不同,out关键字的变量必须有值或必须 引用一个对象或null 它的传递以及不需要 传递之前具有值或引用对象。

          【讨论】:

            【解决方案14】:

            出局: 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();
            
                  }
               }
            }
            

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

            在 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();
            
                  }
               }
            }
            

            【讨论】:

              【解决方案15】:

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

              • 不需要 refout 如果您要做的只是修改传入的MyClass 实例内部论点someClass

                • 无论您使用refout 还是什么都不用,调用方法都会看到类似someClass.Message = "Hello World" 的变化
                • myFunction(someClass) 中写入someClass = new MyClass() 仅在myFunction 方法的范围内换出someClass 看到的对象。调用方法仍然知道它创建并传递给您的方法的原始 MyClass 实例
              • 需要refout,如果您打算将someClass 换成一个全新的对象并希望调用方法看到您的更改

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

              存在其他程序员

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

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

              • 使用out 会声明“将占位符变量传递给我的方法。它是否有值无关紧要;编译器会强制我将其分配给新值。我绝对保证在你调用我的方法之前你的变量指向的对象,在我完成时会有所不同

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

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


              Microsoft 使用数字类型的 .TryParse 方法做到了这一点:

              int i = 98234957;
              bool success = int.TryParse("123", out i);
              

              通过将参数标记为 out,他们在此积极声明“我们肯定将更改您精心设计的 98234957 值以换取其他东西”

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

              public void PoorlyNamedMethod(out SomeClass x)
              

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

              SomeClass x = SpendHoursMakingMeAPerfectSomeClass();
              //now give it to the library
              PoorlyNamedMethod(out x);
              

              那是浪费时间,花费所有时间来完成完美的课程。肯定会被丢掉,取而代之的是PoorlyNamedMethod

              【讨论】:

                【解决方案16】:

                ref 和 out 就像 C++ 中的引用传递和指针传递一样工作。

                对于 ref,参数必须声明和初始化。

                对于out,参数必须声明,但可以初始化也可以不初始化

                        double nbr = 6; // if not initialized we get error
                        double dd = doit.square(ref nbr);
                
                        double Half_nbr ; // fine as passed by out, but inside the calling  method you initialize it
                        doit.math_routines(nbr, out Half_nbr);
                

                【讨论】:

                • 可以内联声明变量:out double Half_nbr.
                【解决方案17】:

                创作时间:

                (1)我们创建调用方法Main()

                (2) 它创建一个List对象(它是一个引用类型的对象)并将它存储在变量myList中。

                public sealed class Program 
                {
                    public static Main() 
                    {
                        List<int> myList = new List<int>();
                

                运行时:

                (3) 运行时在 #00 的堆栈上分配内存,其宽度足以存储地址(#00 = myList,因为变量名实际上只是内存位置的别名)

                (4)运行时在内存位置#FF的堆上创建一个列表对象(所有这些地址都是为了举例)

                (5) 然后运行时将对象的起始地址#FF 存储在#00 处(或者换句话说,将List 对象的引用存储在指针myList 中)

                返回创作时间:

                (6) 然后我们将 List 对象作为参数 myParamList 传递给被调用的方法 modifyMyList 并为其分配一个新的 List 对象

                List<int> myList = new List<int>();
                
                List<int> newList = ModifyMyList(myList)
                
                public List<int> ModifyMyList(List<int> myParamList){
                     myParamList = new List<int>();
                     return myParamList;
                }
                

                运行时:

                (7) 运行时启动被调用方法的调用例程,并在其中检查参数的类型。

                (8) 找到引用类型后,它会在 #04 的堆栈上分配内存,用于给参数变量 myParamList 起别名。

                (9) 然后它将值#FF 也存储在其中。

                (10) 运行时在内存位置#004的堆上创建一个列表对象,并用这个值替换#04中的#FF(或者在这个方法中取消引用原来的List对象并指向新的List对象)

                #00 中的地址不变,保留对#FF 的引用(或原myList 指针不受影响)。


                ref 关键字是一个编译器指令,用于跳过 (8) 和 (9) 的运行时代码生成,这意味着不会为方法参数分配堆.它将使用原始的#00 指针对#FF 处的对象进行操作。如果原始指针未初始化,则运行时将停止抱怨它无法继续,因为变量未初始化

                out 关键字是一个编译器指令,它与 ref 几乎相同,只是在 (9) 和 (10) 处稍作修改。编译器希望参数未初始化,并将继续使用 (8)、(4) 和 (5) 在堆上创建对象并将其起始地址存储在参数变量中。不会抛出未初始化的错误,并且之前存储的任何引用都将丢失。

                【讨论】:

                  【解决方案18】:

                  为了说明许多出色的解释,我开发了以下控制台应用程序:

                  using System;
                  using System.Collections.Generic;
                  
                  namespace CSharpDemos
                  {
                    class Program
                    {
                      static void Main(string[] args)
                      {
                        List<string> StringList = new List<string> { "Hello" };
                        List<string> StringListRef = new List<string> { "Hallo" };
                  
                        AppendWorld(StringList);
                        Console.WriteLine(StringList[0] + StringList[1]);
                  
                        HalloWelt(ref StringListRef);
                        Console.WriteLine(StringListRef[0] + StringListRef[1]);
                  
                        CiaoMondo(out List<string> StringListOut);
                        Console.WriteLine(StringListOut[0] + StringListOut[1]);
                      }
                  
                      static void AppendWorld(List<string> LiStri)
                      {
                        LiStri.Add(" World!");
                        LiStri = new List<string> { "¡Hola", " Mundo!" };
                        Console.WriteLine(LiStri[0] + LiStri[1]);
                      }
                  
                      static void HalloWelt(ref List<string> LiStriRef)
                       { LiStriRef = new List<string> { LiStriRef[0], " Welt!" }; }
                  
                      static void CiaoMondo(out List<string> LiStriOut)
                       { LiStriOut = new List<string> { "Ciao", " Mondo!" }; }
                     }
                  }
                  /*Output:
                  ¡Hola Mundo!
                  Hello World!
                  Hallo Welt!
                  Ciao Mondo!
                  */
                  
                  • AppendWorld:传递了名为LiStriStringList 副本。在 方法开始时,此副本引用原始列表和 因此可以用来修改这个列表。后来LiStri参考 方法中的另一个 List&lt;string&gt; 对象不影响 原始列表。

                  • HalloWelt:LiStriRef是已经初始化的别名 ListStringRef。传递的List&lt;string&gt; 对象用于初始化一个 新的,因此ref 是必要的。

                  • CiaoMondo:LiStriOutListStringOut 的别名,必须是 已初始化。

                  所以,如果一个方法只是修改了被传递变量引用的对象,编译器不会让你使用out,你也不应该使用ref,因为它不会混淆编译器而是代码的读者。如果该方法将使传递的参数引用另一个对象,请使用 ref 表示已初始化的对象,使用 out 表示必须为传递的参数初始化新对象的方法。除此之外,refout 的行为相同。

                  【讨论】:

                    【解决方案19】:

                    它们几乎相同 - 唯一的区别是作为 out 参数传递的变量不需要初始化,使用 ref 参数的方法必须将其设置为某个值。

                    int x;    Foo(out x); // OK 
                    int y;    Foo(ref y); // Error
                    

                    Ref 参数用于可能被修改的数据,out 参数用于作为函数(例如 int.TryParse)的附加输出的数据,这些数据已经在使用返回值。

                    【讨论】:

                      【解决方案20】:

                      参考: ref 关键字用于将参数作为引用传递。这意味着当该参数的值在方法中更改时,它会反映在调用方法中。使用 ref 关键字传递的参数必须在调用方法中初始化,然后才能传递给被调用方法。

                      输出: out 关键字也用于传递参数,如 ref 关键字,但参数可以在不分配任何值的情况下传递。使用 out 关键字传递的参数必须在被调用方法中初始化,然后才能返回调用方法。

                      public class Example
                      {
                       public static void Main() 
                       {
                       int val1 = 0; //must be initialized 
                       int val2; //optional
                      
                       Example1(ref val1);
                       Console.WriteLine(val1); 
                      
                       Example2(out val2);
                       Console.WriteLine(val2); 
                       }
                      
                       static void Example1(ref int value) 
                       {
                       value = 1;
                       }
                       static void Example2(out int value) 
                       {
                       value = 2; 
                       }
                      }
                      
                      /* Output     1     2     
                      

                      方法重载中的引用和输出

                      ref 和 out 不能同时用于方法重载。然而, ref 和 out 在运行时被不同地对待,但在编译时它们被同样对待(CLR 在为 ref 和 out 创建 IL 时不会区分两者)。

                      【讨论】:

                        【解决方案21】:

                        下面我展示了一个同时使用 Refout 的示例。现在,你们都将被清除关于 ref 和 out 的信息。

                        在下面提到的例子中,当我评论 //myRefObj = new myClass { Name = "ref outside called!!" }; 行,会得到一个错误提示“Use of unassigned local variable 'myRefObj'”,但是out中没有这样的错误。

                        在哪里使用 Ref:当我们调用带有 in 参数的过程时,将使用相同的参数来存储该过程的输出。

                        在哪里使用 Out: 当我们调用没有 in 参数的过程时,将使用相同的参数从该过程返回值。 还要注意输出

                        public partial class refAndOutUse : System.Web.UI.Page
                        {
                            protected void Page_Load(object sender, EventArgs e)
                            {
                                myClass myRefObj;
                                myRefObj = new myClass { Name = "ref outside called!!  <br/>" };
                                myRefFunction(ref myRefObj);
                                Response.Write(myRefObj.Name); //ref inside function
                        
                                myClass myOutObj;
                                myOutFunction(out myOutObj);
                                Response.Write(myOutObj.Name); //out inside function
                            }
                        
                            void myRefFunction(ref myClass refObj)
                            {
                                refObj.Name = "ref inside function <br/>";
                                Response.Write(refObj.Name); //ref inside function
                            }
                            void myOutFunction(out myClass outObj)
                            {
                                outObj = new myClass { Name = "out inside function <br/>" }; 
                                Response.Write(outObj.Name); //out inside function
                            }
                        }
                        
                        public class myClass
                        {
                            public string Name { get; set; }
                        } 
                        

                        【讨论】:

                          【解决方案22】:
                           public static void Main(string[] args)
                              {
                                  //int a=10;
                                  //change(ref a);
                                  //Console.WriteLine(a);
                                  // Console.Read();
                          
                                  int b;
                                  change2(out b);
                                  Console.WriteLine(b);
                                  Console.Read();
                              }
                              // static void change(ref int a)
                              //{
                              //    a = 20;
                              //}
                          
                               static void change2(out int b)
                               {
                                   b = 20;
                               }
                          

                          您可以检查此代码,它将向您描述其完全不同之处 当您使用“ref”时,这意味着您已经初始化了该 int/string

                          但是 当你使用“out”时 它在两种情况下都有效,无论您是否初始化该 int/string 但是您必须在该函数中初始化该 int/string

                          【讨论】:

                            【解决方案23】:

                            我想举例说明两个主要区别:

                            1. refout 通过引用传递,hense;
                             class Program
                                {
                                    public static void Main(string[] args)
                                    {
                                        var original = new ObjectWithMememberList(3);
                                        Console.WriteLine(original.MyList.Capacity); // 3
                                        ChangeList(original.MyList);
                                        Console.WriteLine(original.MyList.Capacity); // 3
                                    }
                            
                                    static void ChangeList(List<int> vr)
                                    {
                                        vr = new List<int>(2);
                                    }
                            }
                            

                            但是:

                             class Program
                                {
                                    public static void Main(string[] args)
                                    {
                                        var original = new ObjectWithMememberList(3);
                                        Console.WriteLine(original.MyList.Capacity); // 3
                                        ChangeList(ref original.MyList);
                                        Console.WriteLine(original.MyList.Capacity); // 2
                                    }
                            
                                    static void ChangeList(ref List<int> vr)
                                    {
                                        vr = new List<int>(2);
                                    }
                            }
                            

                            out 相同。 2. ref 参数必须是可赋值变量。 所以:

                            ChangeList(ref new List<int>()); // Compile Error [might not be initialized before accessing]
                            

                            但是:

                            List<int> xs;
                            ChangeList(out xs); // Compiles
                            

                            【讨论】:

                              【解决方案24】:

                              从接收参数的方法来看,refout的区别在于C#要求方法在返回之前必须写入每个out参数,并且不能对这样的参数做任何事情,除了将它作为out 参数传递或写入它,直到它作为out 参数传递给另一个方法或直接写入。请注意,其他一些语言没有这样的要求;在 C# 中使用 out 参数声明的虚拟或接口方法可以用另一种不对此类参数施加任何特殊限制的语言覆盖。

                              从调用者的角度来看,在许多情况下,C# 会假设在调用带有out 参数的方法时,会导致在未先读取之前写入传递的变量。当调用用其他语言编写的方法时,这种假设可能不正确。例如:

                              struct MyStruct
                              {
                                 ...
                                 myStruct(IDictionary<int, MyStruct> d)
                                 {
                                   d.TryGetValue(23, out this);
                                 }
                              }
                              

                              如果 myDictionary 识别出用非 C# 语言编写的 IDictionary&lt;TKey,TValue&gt; 实现,即使 MyStruct s = new MyStruct(myDictionary); 看起来像一个赋值,它也可能使 s 保持不变。

                              请注意,用 VB.NET 编写的构造函数与 C# 中的构造函数不同,它不会假设被调用的方法是否会修改任何 out 参数,并且会无条件地清除所有字段。上面提到的奇怪行为不会发生在完全用 VB 或完全用 C# 编写的代码中,但是当用 C# 编写的代码调用用 VB.NET 编写的方法时会发生。

                              【讨论】:

                                【解决方案25】:

                                如果您想将参数作为 ref 传递,那么您应该在将参数传递给函数之前对其进行初始化,否则编译器本身会显示错误。但在 out 参数的情况下,您不需要在传递之前初始化对象参数方法。您可以在调用方法本身中初始化对象。

                                【讨论】:

                                  【解决方案26】:

                                  请注意,在函数内部传递的引用参数是直接处理的。

                                  例如,

                                      public class MyClass
                                      {
                                          public string Name { get; set; }
                                      }
                                  
                                      public void Foo()
                                      {
                                          MyClass myObject = new MyClass();
                                          myObject.Name = "Dog";
                                          Bar(myObject);
                                          Console.WriteLine(myObject.Name); // Writes "Dog".
                                      }
                                  
                                      public void Bar(MyClass someObject)
                                      {
                                          MyClass myTempObject = new MyClass();
                                          myTempObject.Name = "Cat";
                                          someObject = myTempObject;
                                      }
                                  

                                  这将写狗,而不是猫。因此,您应该直接在 someObject 上工作。

                                  【讨论】:

                                  • 虽然这里的所有内容都非常真实,但它并不能真正解释按值引用或输出之间的区别。充其量它解释了引用和值/不可变类型之间的区别。
                                  • 如果您希望该代码编写 cat,请将该对象与“ref”键一起传递,如下所示:public static void Bar(ref MyClass someObject), Bar(ref myObject);
                                  【解决方案27】:

                                  我可能不太擅长这个,但字符串(即使它们在技术上是引用类型并且存在于堆上)肯定是按值传递的,而不是按引用传递?

                                          string a = "Hello";
                                  
                                          string b = "goodbye";
                                  
                                          b = a; //attempt to make b point to a, won't work.
                                  
                                          a = "testing";
                                  
                                          Console.WriteLine(b); //this will produce "hello", NOT "testing"!!!!
                                  

                                  这就是为什么你需要 ref 如果你希望更改存在于创建它们的函数范围之外,否则你不会传递引用。

                                  据我所知,您只需要结构/值类型和字符串本身的 ref,因为字符串是一种假装它是但不是值类型的引用类型。

                                  我在这里可能完全错了,我是新手。

                                  【讨论】:

                                  • 欢迎来到 Stack Overflow,埃德温。据我所知,字符串是通过引用传递的,就像任何其他对象一样。您可能会感到困惑,因为字符串是不可变的对象,因此它们通过引用传递并不那么明显。想象一下,字符串有一个名为Capitalize() 的方法,可以将字符串的内容更改为大写字母。如果您随后将行 a = "testing"; 替换为 a.Capitalize();,那么您的输出将是“HELLO”,而不是“Hello”。不可变类型的优点之一是您可以传递引用,而不必担心其他代码会更改值。
                                  • 一个类型可以暴露三种基本的语义类型:可变引用语义、可变值语义和不可变语义。考虑具有字段或属性 m 的类型 T 的变量 x 和 y,并假设 x 被复制到 y。如果 T 具有引用语义,则 y.m 将观察到 x.m 的变化。如果 T 具有值语义,则可以更改 x.m 而不会影响 y.m。如果 T 具有不可变的语义,则 x.m 和 y.m 都不会改变。不可变语义可以通过引用或值对象来模拟。字符串是不可变的引用对象。
                                  猜你喜欢
                                  • 1970-01-01
                                  • 1970-01-01
                                  • 2014-04-28
                                  • 1970-01-01
                                  • 2016-02-22
                                  • 1970-01-01
                                  • 2019-04-05
                                  • 2014-05-26
                                  • 2015-07-28
                                  相关资源
                                  最近更新 更多