首页 文章

's the difference between the ' ref ' and ' out'关键字?

提问于
浏览
766

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

public void myFunction(ref MyClass someClass)

public void myFunction(out MyClass someClass)

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

24 回答

  • 5

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

    因此虽然 ref 是双向的,但 out 是唯一的 .

  • 4

    ref 修饰符意味着:

    • 该值已设置

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

    out 修饰符意味着:

    • 该方法读取的值不是't set and can',直到设置为止 .

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

  • -3

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

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

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

  • 26

    我将尝试解释一下:

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

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

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

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

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

    • 保存某个对象数据的内存块 .

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

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

    现在,当您通过引用方法发送someobject时会发生什么?某些对象的 actual reference 被发送到该方法 . 所以你现在只有一个数据引用:

    (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
    
  • 134

    ref is in and out .

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

  • 0

    出:

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

    • 您无需初始化调用函数中的值 .

    • 您必须在被调用函数中分配值,否则编译器将报告错误 .

    ref:

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

    • 您需要在调用函数之前初始化变量 .

    • 不必为方法中的ref参数赋值 . 如果您不更改该值,需要将其标记为“ref”?

  • 11

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

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

    例:

    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 .

  • 1

    除了以下差异之外, refout 表现相似 .

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

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

  • 1

    “贝克”

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

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

    string s = "Able";
    

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

    s = "Baker";
    

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

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

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

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

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

  • 4

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

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

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

  • 1

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

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

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


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

  • 0

    Authoring Time:

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

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

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

    During Runtime:

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

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

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

    Back to Authoring Time:

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

    During Runtime:

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

  • 47

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

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

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

  • 5
    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

  • 14

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

    Out: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时不区分这两者) .

  • 7

    下面我展示了使用 Refout 的示例 . 现在,你们都将被清除关于ref和out .

    在下面提到的例子中,当我评论 //myRefObj = new myClass ; 行时,会收到错误说 "Use of unassigned local variable 'myRefObj'" ,但 out 中没有这样的错误 .

    Where to use Ref :当我们使用in参数调用过程时,将使用相同的参数来存储该proc的输出 .

    Where to use 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!!  
    " }; 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
    "; Response.Write(refObj.Name); //ref inside function } void myOutFunction(out myClass outObj) { outObj = new myClass { Name = "out inside function
    " }; Response.Write(outObj.Name); //out inside function } } public class myClass { public string Name { get; set; } }
  • -1

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

  • -3

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

  • 0

    我正在玩 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)
        {
    
        }
    }
    
  • 1019

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

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

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

  • 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 .

相关问题