971

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

public void myFunction(ref MyClass someClass)

public void myFunction(out MyClass someClass)

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

4

27 回答 27

1254

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

所以whileref是双向的,out是out-only。

于 2008-12-23T09:18:03.860 回答
594

ref修饰符意味着:

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

out修饰符意味着:

  1. 该值未设置,并且在设置之前无法由该方法读取。
  2. 该方法必须在返回之前设置它。
于 2008-12-23T12:40:41.767 回答
152

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

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

如果 Dom 是个吵架者,他会让 Peter 打印一份新的备忘录,让他随身携带。

于 2010-03-23T19:33:41.770 回答
65

我将尝试解释:

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

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

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

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

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

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

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

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

指向同一个对象的两个引用。如果您使用 reference2 修改某个对象的属性,它将影响 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
于 2010-09-23T17:55:58.487 回答
29

ref 是 inout

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

于 2008-12-23T09:17:32.783 回答
21

出去:

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

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

参考:

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

  1. 您需要在调用函数之前初始化变量。
  2. 不必为方法中的 ref 参数分配任何值。如果不改变值,有什么必要将其标记为“ref”?
于 2016-04-01T18:31:04.407 回答
14

扩展狗,猫的例子。带有 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;
    }
于 2010-05-19T13:35:17.917 回答
10

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


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

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

于 2014-12-06T19:21:51.323 回答
8

ref并且out行为相似,除了以下差异。

  • ref变量必须在使用前初始化。out变量可以不赋值使用
  • out使用它的函数必须将参数视为未分配的值。因此,我们可以out在调用代码中使用初始化参数,但函数执行时该值会丢失。
于 2014-02-10T16:29:27.487 回答
8

对于那些通过例子来学习的人(比如我),这就是Anthony Kolesov 所说的

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

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

于 2015-12-25T21:16:51.400 回答
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只要您传入一个类,如果您想更改方法中的对象,您就不必使用它。

于 2008-12-23T10:57:23.423 回答
6

“贝克”

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

字符串起初看起来有些特别。但是字符串只是一个参考类,如果你定义

string s = "Able";

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

s = "Baker";

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

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

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

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

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

于 2008-12-23T13:40:23.143 回答
6

对于那些寻找简洁答案的人。

ref和关键字都out用于传递 - reference


关键字的变量ref必须有一个值或必须引用一个对象或null 其传递之前。


与 不同ref的是,关键字的变量out必须有值或必须引用对象或null 其传递之后以及在传递之前不需要有值或引用对象。

于 2019-11-26T07:49:35.183 回答
5

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

      }
   }
}
于 2014-10-25T16:57:43.860 回答
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);
于 2015-03-15T08:49:40.000 回答
4

创作时间:

(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的堆上创建一个list对象,并用这个值替换#04中的#FF(或者在这个方法中取消引用原来的List对象并指向新的List对象)

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


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

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

于 2017-05-29T16:10:11.487 回答
4

除了允许您将其他人的变量重新分配给类的不同实例、返回多个值等,使用refout让其他人知道您需要他们做什么以及您打算如何处理他们提供的变量

  • 不需要 ref,或者out您要做的只是修改在参数中传递的实例的内容。MyClasssomeClass

    • 调用方法将看到更改,例如someClass.Message = "Hello World"您是否使用或什么都不使用refout
    • 写入someClass = new MyClass()inside只myFunction(someClass)换出方法someClass范围内的对象myFunction。调用方法仍然知道MyClass它创建并传递给您的方法的原始实例
  • 需要 ref或者out如果您计划将其换成someClass一个全新的对象并希望调用方法看到您的更改

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

存在其他程序员

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

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

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

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

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


微软使用.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

于 2019-08-14T14:52:33.987 回答
3

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

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!
*/
  • AppendWorldStringList: 一个named的副本LiStri通过了。在方法开始时,此副本引用原始列表,因此可用于修改此列表。稍后在方法中LiStri引用另一个List<string>不影响原始列表的对象。

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

  • CiaoMondo:LiStriOut是一个别名,ListStringOut必须初始化。

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

于 2019-10-10T09:46:34.863 回答
2

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

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

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

于 2013-06-20T16:06:52.977 回答
2

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 不能同时用于方法重载。但是, ref 和 out 在运行时的处理方式不同,但在编译时它们的处理方式相同(CLR 在为 ref 和 out 创建 IL 时不会区分两者)。

于 2015-07-26T23:22:38.197 回答
1

下面我展示了一个同时使用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; }
} 
于 2013-06-25T11:18:27.427 回答
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;
     }

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

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

于 2014-11-13T09:17:21.130 回答
1

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

  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
于 2021-05-29T11:25:45.507 回答
0

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

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

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

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

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

于 2015-02-11T22:51:21.360 回答
0

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

于 2016-12-05T09:57:33.137 回答
-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;
    }

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

于 2010-04-14T23:15:39.813 回答
-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 如果您希望更改存在于创建它们的函数范围之外,否则您不会传递引用。

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

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

于 2011-12-08T18:36:51.027 回答