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

时间:2008-12-23 09:16:44

标签: c# reference keyword out ref

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

public void myFunction(ref MyClass someClass)

public void myFunction(out MyClass someClass)

我应该使用哪种?为什么?

27 个答案:

答案 0 :(得分:1081)

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

因此虽然ref是双向的,但out只是出局。

答案 1 :(得分:485)

ref修饰符表示:

  1. 该值已设置且
  2. 该方法可以读取和修改它。
  3. out修饰符表示:

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

答案 2 :(得分:138)

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

如果Dom是一个参考论证,他会有一份备忘录的打印副本。

如果Dom出了问题,他会让彼得打印一份新的备忘录,供他随身携带。

答案 3 :(得分:51)

我将试着解释一下:

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

对于令人困惑的部分,参考类型:

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

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

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

  1. 包含 someobject 数据的内存块。
  2. 该块的引用(指针) 数据。
  3. 现在,当您将 someobject 发送到没有参考的方法时,它会复制引用指针,而不是数据。所以你现在有了这个:

    (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
    

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

    (outside method) reference1 => someobject;
    (inside method)  reference1 => 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
    

答案 4 :(得分:27)

ref is in and out

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

答案 5 :(得分:16)

出:

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

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

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

    1. 在调用函数之前,需要初始化变量。
    2. 不必为方法中的ref参数指定任何值。如果您不更改该值,需要将其标记为“ref”?

答案 6 :(得分:12)

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

答案 7 :(得分: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";
}

只要您传入课程,如果您想更改方法中的对象,就不必使用ref

答案 8 :(得分:8)

除了以下差异之外,

refout的行为相似。

  • ref变量必须在使用前初始化。 out变量可以在没有赋值的情况下使用
  • out参数必须被使用它的函数视为未分配的值。因此,我们可以在调用代码中使用初始化的out参数,但在函数执行时该值将丢失。

答案 9 :(得分:7)

对于那些通过实例学习的人(像我一样),这里是Anthony Kolesov is saying

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

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

答案 10 :(得分:6)

“贝克”

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

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

string s = "Able";

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

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

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

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

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

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

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

答案 11 :(得分:5)

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


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

答案 12 :(得分:4)

<强>输出: 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();

      }
   }
}

<强> 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();

      }
   }
}

答案 13 :(得分:4)

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

答案 14 :(得分:3)

创作时间:

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

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

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

在运行时:

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

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

(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)在堆上创建对象并将其起始地址存储在参数变量中。不会抛出未初始化的错误,任何先前存储的引用都将丢失。

答案 15 :(得分:3)

针对那些简洁明了的答案。

  

refout这两个关键字都用于传递reference

     
     

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

     
     

ref不同,out关键字的变量必须具有值或必须   引用对象或null 之后,并且不需要   通过之前具有值或引用对象。

答案 16 :(得分:2)

除了允许您将其他人的变量重新分配给类的其他实例,返回多个值等之外,使用refout还可以使其他人从他们那里了解您的需求以及您打算使用它们提供的变量

  • 不需要 refout,如果您要做的只是修改 {在参数MyClass中传递的{1}}个实例。

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

    • someClass内书写someClass = new MyClass()会更改通过调用myFunction(out someClass)的方法看到的对象

存在其他程序员

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

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

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

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

这可以防止该方法将传入的实例换成其他实例。想起来就像对数百万的开发人员说:“将您原始的变量引用传递给我,我保证不会将您精心制作的数据换成其他东西”。 in具有一些特殊性,在某些情况下,例如可能需要隐式转换以使您的short与in兼容,编译器会临时生成一个int,扩大您的short,然后传递它通过参考并完成。之所以可以这样做,是因为您已经声明自己不会弄乱它。


Microsoft使用in int方法处理数字类型:

.TryParse

通过将参数标记为int i = 98234957; bool success = int.TryParse("123", out i); ,他们在这里积极声明“我们肯定会将您精心设计的98234957的值更改为其他内容”

当然,它们对于诸如解析值类型之类的东西来说一定是必须的,因为如果不允许使用parse方法将值类型换成其他值,它将无法很好地工作。您正在创建的某些库中的方法:

out

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

out

那真是浪费时间,要花所有的时间才能做到完美。它肯定会被扔掉,取而代之的是PoorlyNamedMethod

答案 17 :(得分:2)

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

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:传递了名为StringList的{​​{1}}的副本。在 方法的开始,此副本引用原始列表,并且 因此可以用来修改此列表。以后的LiStri参考 方法内的另一个LiStri对象,不影响 原始列表。

  • List<string>HalloWelt是已初始化的别名 LiStriRef。传递的ListStringRef对象用于初始化 新的,因此List<string>是必要的。

  • refCiaoMondoLiStriOut的别名,必须为 初始化。

因此,如果方法仅修改了传递的变量所引用的对象,则编译器将不允许您使用ListStringOut,并且您不应使用out,因为它会使编译器而不是读者感到困惑代码。如果该方法将使传递的参数引用另一个对象,请对已初始化的对象使用ref,对于必须为传递的参数初始化新对象的方法,请使用ref。除此之外,outref的行为相同。

答案 18 :(得分:1)

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

您可以查看此代码,它将描述您的完全不同 当你使用&#34; ref&#34;它的意思是你已经初始化了int / string

但  当你使用&#34; out&#34; 它可以在两种情况下工作,你是否初始化了int / string 但是你必须在该函数中初始化那个int / string

答案 19 :(得分:1)

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

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

参考参数用于可能被修改的数据,out参数用于数据,该数据是已经使用某些东西的返回值的函数的额外输出(例如int.TryParse)。

答案 20 :(得分:1)

价: 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 :(得分:0)

下面我展示了使用参考输出的示例。现在,你们都将被清除关于ref和out。

在下面提到的例子中,我发表评论 // myRefObj = new myClass {Name =“ref outside outside !!”}; 如果使用未分配的本地变量'myRefObj',则会出现错误,但 out 中没有此类错误。

何处使用参考:当我们使用in参数调用过程时,将使用相同的参数来存储该过程的输出。

在何处使用Out:当我们调用一个没有in参数的过程时,同样的param将用于从该proc返回值。 还要注意输出

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 :(得分:0)

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

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

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

如果myDictionary标识用C#以外的语言编写的IDictionary<TKey,TValue>实现,即使MyStruct s = new MyStruct(myDictionary);看起来像是一项任务,也可能会s未修改。

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

答案 23 :(得分:0)

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

答案 24 :(得分:0)

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

  1. refout 通过引用传递,母鸡;
 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

答案 25 :(得分:-3)

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

例如,

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

这会写Dog,而不是Cat。因此,您应该直接使用someObject。

答案 26 :(得分:-4)

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

        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,因为string是一个假装它但不是值类型的引用类型。

虽然我可能完全错了,但我是新人。