提问



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


public void myFunction(ref MyClass someClass)





public void myFunction(out MyClass someClass)


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

最佳参考


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


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

其它参考1


ref修饰符表示:



  1. 该值已设置且

  2. 该方法可以读取和修改它。



out修饰符表示:



  1. 该值未设置且无法通过方法读取,直到设置为止。

  2. 方法必须在返回之前设置它。


其它参考2


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


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


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

其它参考3


我将尝试解释一下:


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


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


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


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


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



  1. 包含 someobject 数据的内存块。

  2. 该块的引用(指针)
    数据。



现在当您将 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


ref在中。[66]


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

其它参考5


出:



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



  1. 您无需在调用函数中初始化值。

  2. 您必须在被调用函数中指定值,否则编译器将报告错误。



REF:



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



  1. 在调用函数之前,需要初始化变量。

  2. 不必为方法中的ref参数指定任何值。如果您不更改该值,需要将其标记为ref?


其它参考6


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


由于您在传入引用类型(类)时不需要使用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


refout表现相似,只是遵循差异。



  • ref变量必须在使用前初始化。 out变量可以在没有赋值的情况下使用

  • out参数必须被使用它的函数视为未赋值。因此,我们可以在调用代码中使用initialized out参数,但是当函数执行时该值将丢失。


其它参考9


贝克


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


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


string s = "Able";


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


s = "Baker";


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


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


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


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


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

其它参考10


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

      }
   }
}

其它参考11


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

其它参考12


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


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


https://gist.github.com/2upmedia/6d98a57b68d849ee7091[68]

其它参考13


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





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


在这里阅读。[69]

其它参考14


创作时间:


(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


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


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


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

其它参考16


 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

其它参考17


参考:
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 in方法重载


ref和out都不能同时用于方法重载。但是,ref和out在运行时被区别对待,但它们在编译时被视为相同(CLR在为ref和out创建IL时不区分这两者)。

其它参考18


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


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


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


在哪里使用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; }
} 

其它参考19


从接收参数的方法的角度来看,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编写的方法时会发生。

其它参考20


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

其它参考21


我正在玩ref,发现这个例子非常有趣。
我认为RefEater(ref s1);的调用将导致构建错误,如第二个注释的情况,但s1字段在调用构造函数之前被初始化为其默认值(https://stackoverflow.com/a/1920659/5612780).


public class Class1
{
    // this will have the default value
    private string s1;

    public Class1()
    {
        // no issue here..
        RefEater(ref s1);

        // Error CS0165 Use of unassigned local variable 's2'
        //string s2;
        //RefEater(ref s2);
    }

    private void RefEater(ref string s)
    {

    }
}

其它参考22


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


        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 for structs/value类型和字符串本身,因为string是一个假装它但不是值类型的引用类型。


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

其它参考23


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


例如,


    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。