关于c#:”ref”和”out”关键字有什么区别?

What's the difference between the 'ref' and 'out' keywords?

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

1
public void myFunction(ref MyClass someClass)

1
public void myFunction(out MyClass someClass)

我应该使用哪一个?为什么?


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

因此,虽然ref是两种方式,但out只是一种方式。


ref修饰语是指:

  • 该值已设置,并且
  • 该方法可以读取和修改它。
  • out修饰语是指:

  • 该值未设置,并且在设置之前无法由方法读取。
  • 方法必须在返回前设置它。

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

    如果dom是ref参数,他会有一份备忘录的打印件。

    如果多姆不同意,他会让彼得打印一份新的备忘录,让他随身携带。


    我要试着解释一下:

    我认为我们理解价值类型是如何工作的?值类型为(int、long、struct等)。当您在没有引用命令的情况下将它们发送到函数时,它会复制数据。在函数中对该数据所做的任何操作都只影响副本,而不是原始数据。REF命令发送实际数据,任何更改都将影响函数外部的数据。

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

    允许创建引用类型:

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

    当您新建某个对象时,将创建两个部分:

  • 为某个对象保存数据的内存块。
  • 指向该块的引用(指针)数据。
  • 现在,当您将某个对象发送到一个没有引用的方法中时,它会复制引用指针,而不是数据。现在你有了这个:

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

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

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

    如果您将reference2设为空或指向新数据,则不会影响reference1或数据reference1指向。

    1
    2
    3
    4
    5
    6
    (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的实际引用将被发送到该方法。所以您现在只有一个对数据的引用:

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

    但这意味着什么?它的作用与发送某个对象完全相同,而不是通过引用,除了两件主要事情:

    1)当方法内的引用为空时,方法外的引用将为空。

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

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

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


    裁判进出。

    在满足您的需求的地方,您应该优先使用out


    出:

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

  • 您不需要初始化调用函数中的值。
  • 必须在被调用的函数中赋值,否则编译器将报告错误。
  • 裁判:

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

  • 在调用函数之前需要初始化变量。
  • 方法中的ref参数不必指定任何值。如果不更改值,需要将其标记为"Ref"吗?

  • 延伸狗,猫的例子。带有ref的第二个方法更改了调用方引用的对象。因此"猫"!!!!

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
        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;
        }

    除以下差异外,refout的表现类似。

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

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

    例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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


    对于那些以身作则的人(像我一样),这里是安东尼·科里索夫所说的。

    我已经创建了一些引用、out和其他的最小示例来说明这一点。我不介绍最佳实践,只是举例来了解不同之处。

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


    "Baker"

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

    一开始字符串看起来有点特别。但字符串只是一个引用类,如果您定义

    1
    string s ="Able";

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

    1
    s ="Baker";

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

    您可以通过下面的小代码示例来尝试:

    1
    2
    3
    4
    string s ="Able";
    string s2 = s;
    s ="Baker";
    Console.WriteLine(s2);

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

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


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

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


    REF和OUT工作就像传递引用和指针通过C++一样。

    对于引用,参数必须声明并初始化。

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

    1
    2
    3
    4
    5
            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);


    出:RETURN语句只能用于从函数返回一个值。但是,使用输出参数,可以从函数返回两个值。输出参数与引用参数类似,只是它们将数据从方法中传递出来,而不是传递到方法中。

    下面的示例说明了这一点:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    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关键字声明引用参数。下面的示例演示了这一点:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    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();

          }
       }
    }


    创作时间:

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

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

    1
    2
    3
    4
    5
    public sealed class Program
    {
        public static Main()
        {
            List<int> myList = new List<int>();

    运行期间:

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

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

    (5)运行时将对象的起始地址存储在00(或者换句话说,将列表对象的引用存储在指针myList中)。

    回到创作时间:

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

    1
    2
    3
    4
    5
    6
    7
    8
    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)然后,它也将值存储在其中。

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

    00中的地址没有更改,保留了对FF的引用(或原始myList指针没有被干扰)。

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

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


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

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

    Ref参数用于可能被修改的数据,Out参数用于已经使用返回值的函数(例如int.typarse)的附加输出数据。


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
     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"时,它意味着u已经初始化了int/string

    但是当你使用"out"时无论您是否初始化int/string,它都可以在这两种情况下工作。但是u必须初始化函数中的int/string


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

    出:out关键字还用于传递像ref关键字这样的参数,但是可以在不给参数赋值的情况下传递该参数。使用out关键字传递的参数必须在被调用方法中初始化,然后才能返回到调用方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    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。但是,在运行时对ref和out的处理是不同的,但在编译时对它们的处理是相同的(clr在为ref和out创建il时不区分这两者)。


    从接收参数的方法的角度来看,refout之间的区别在于,c要求方法必须在返回之前写入每个out参数,并且除将其作为out参数传递或写入该参数外,不得对该参数执行任何操作,直到该参数被EIT接收为止。她作为一个out参数传递给另一个方法或直接写入。请注意,有些其他语言没有强加这样的要求;用c声明的带有out参数的虚拟或接口方法可以用不对这些参数施加任何特殊限制的其他语言重写。

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

    1
    2
    3
    4
    5
    6
    7
    8
    struct MyStruct
    {
       ...
       myStruct(IDictionary<int, MyStruct> d)
       {
         d.TryGetValue(23, out this);
       }
    }

    如果myDictionary标识用C语言以外的语言编写的IDictionary实现,即使MyStruct s = new MyStruct(myDictionary);看起来像一个赋值,它可能会使s保持不变。

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


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


    下面我展示了一个使用ref和out的示例。现在,你们都将被清除关于裁判和出局。

    在下面提到的示例中,当我评论//MyRefObj=New MyClass name="Ref Outside Called!"!"行,将得到一个错误,说明"使用未分配的局部变量‘myrefobj’",但在out中没有这样的错误。

    在哪里使用ref:当我们使用in参数调用一个过程时,相同的参数将用于存储该过程的输出。

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    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; }
    }

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

    例如,

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
        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;
        }

    这将写狗,而不是猫。因此,您应该直接处理某个对象。


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
            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"!!!!

    这就是为什么您需要引用的原因如果您希望更改存在于生成它们的函数的作用域之外,那么就不需要传递引用。

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

    不过,我可能完全错了,我是新来的。