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


891

我正在创建一个需要传递对象的函数,以便可以通过该函数对其进行修改。之间有什么区别?

public void myFunction(ref MyClass someClass)

public void myFunction(out MyClass someClass)

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


69
您:我需要传递一个对象,以便可以对其进行修改看起来像是MyClass一个class类型,即引用类型。在这种情况下,您可以通过myFunctionref/ 的偶数来修改您传递的对象,而不带/ out关键字。myFunction将会收到指向相同对象的引用,并且它可以根据需要修改该对象。关键字的不同之处在于,它将收到对相同对象的相同引用。仅当将引用更改为指向另一个对象时,这一点才重要。refmyFunctionmyFunction
Jeppe Stig Nielsen

3
当@AnthonyKolesov的答案非常完美时,我对这里令人困惑的答案感到困惑。
o0'。

当您希望一个方法返回多个值时,声明out方法很有用。可以将一个参数分配为null。这使方法可以选择返回值。
Yevgraf Andreyevich Zhivago 2014年

这里例如,它更容易理解:)解释dotnet-tricks.com/Tutorial/csharp/...
Prageeth godage

2
@JeppeStigNielsen的评论从技术上讲是对OP实际问题的(唯一)正确答案。要将对象传递给方法,以便该方法可以修改该对象,只需按值将(对该对象的)引用传递给方法。即使该方法包含其自己的单独变量(引用同一对象),通过object参数在方法内更改对象也会修改原始对象
David R Tribble,

Answers:


1158

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

因此,虽然ref是双向的,out却是唯一的。


269
特定于out的另一件很酷的事情是该函数必须分配给out参数。不允许将其保留为未分配状态。
Daniel Earwicker

7
“引用”仅适用于值类型吗?由于引用类型始终由ref传递。
故障

3
是。包含结构的值类型
Rune Grimstad'Dec

17
@faulty:不,ref不仅适用于值类型。ref / out就像C / C ++中的指针一样,它们处理对象的内存位置(在C#中间接)而不是直接对象。
THR

52
@faulty:违反直觉,除非使用ref说明符,否则引用类型总是按C#中的值传递。如果设置myval = somenewval,则效果仅在该功能范围内。ref关键字将允许您将myval更改为指向somenewval。
JasonTrue 2010年

534

ref修改意味着:

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

out修改意味着:

  1. 值未设置,在设置之前无法被方法读取。
  2. 该方法必须在返回之前进行设置。

30
该答案最清楚,最简洁地说明了编译器在使用out关键字而不是ref关键字时施加的限制。
维利博士的学徒

5
从MSDN:ref参数必须在使用前初始化,而out参数在传递前不必显式初始化,并且任何先前的值都将被忽略。
希瓦·库玛

1
使用out,如果在调用该方法之前已对其进行了初始化,则可以在该方法设置之前在方法内完全读取它吗?我的意思是,被调用方法可以读取作为参数传递给它的调用方法吗?
Panzercrisis

3
Panzercrisis,对于“ out”,如果已设置,则可以读取被调用的方法。但必须再次设置。
robert jebakumar2

146

假设Dom出现在Peter的小隔间中,展示了有关TPS报告的备忘录。

如果Dom是裁判的论点,他将拥有备忘录的印刷版。

如果Dom提出异议,他会让Peter打印一份新的备忘录供他随身携带。


54
裁判大教堂会用铅笔写的报告,以便彼得能修改
Deebster

6
@Deebster,你知道,这个比喻对你没有做任何事情,为什么你要如此折磨呢?;)
Michael Blackburn

21
娱乐
又有

2
万一有人半信半疑地发现了这个答案,请观看电影“办公室空间”。
displayName

和Dom和Peters的老板将站在Dom的后面(出于争论),迫使双方重新进行打印,直到Peter递给Domd打印输出为止
Patrick

57

我将尝试解释一下:

我认为我们了解值类型的运作方式正确吗?值类型为(int,long,struct等)。当您将它们发送给没有ref命令的函数时,它将复制数据。您对函数中的数据所做的任何操作只会影响副本,而不会影响原始副本。ref命令发送ACTUAL数据,任何更改都会影响该功能之外的数据。

好了,让我们困惑的部分是引用类型:

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

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

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

  1. 存储某个对象的数据的内存块。
  2. 该数据块的引用(指针)。

现在,当你在发送someobject成方法,无需裁判它复制的参考指针,而不是数据。因此,您现在有了:

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

两个引用指向同一对象。如果修改属性someobject使用给定2它会影响通过参考量指向相同的数据。

 (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通过裁判的方法?对某个对象实际引用将发送到该方法。因此,您现在只有一个对数据的引用:

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

但是,这是什么意思?它的行为与不通过ref发送某些对象的行为完全相同,除了两个主要方面:

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

您的意思是,毕竟(在ref情况下)只有一个数据引用,但有两个别名。对?
Sadiq

3
赞成明确的解释。但是我认为这不能回答问题,因为它不能解释refout参数之间的区别。
Joyce Babu

1
惊人。可以和out关键字解释一样吗?
Asif Mushtaq

28

裁判是在

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


不太正确,因为如果没有定向,则可以接受的答案引用是定向的,而忽略值类型则无用。
肯尼2010年

@kenny:能否请您澄清一下-即,您将更改哪些词以保持答案的精神,但消除您认为的不准确之处?我的回答不是来自新手的疯狂猜测,但您评论中的仓促(简洁,错别字)似乎是这样。目的是提供一种以最少的单词来思考差异的方法。
Ruben Bartelink 2010年

(顺便说一句,我对值类型,引用类型,按引用传递,按值传递,COM和C ++熟悉,如果您在澄清中引用这些概念很有用)
Ruben Bartelink 2010年

1
对象引用按值传递(使用“ ref”或“ out”关键字时除外)。将对象视为ID号。如果一个类变量包含“ Object#1943”,并且将其按值传递给例程,则该例程可以对Object#1943进行更改,但不能使变量指向“ Object#1943”以外的任何其他变量。如果变量是通过引用传递的,则例程可使变量点保持“对象#5441”。
2012年

1
@supercat:我很喜欢您对ref vs val的解释(以及这种后续分析)。我认为肯尼实际上不需要给他任何解释,(相对地)像他的评论那样令人困惑。我确实希望我们都可以删除这些该死的评论,尽管它们只会使所有人感到困惑。所有这些胡说八道的根本原因似乎是肯尼误解了我的答案,并且尚未指出应该添加/删除/替换的单词。我们三个人都没有从我们不知道的讨论中学到任何东西,而另一个答案的投票数却很可笑。
Ruben Bartelink

18

出:

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

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

参考:

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

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

“在C#中,一个方法只能返回一个值。如果您想返回多个值,则可以使用out关键字。” 我们还可以使用“ ref”来返回值。因此,如果要从一个方法返回多个值,可以同时使用ref和out?
Ned

1
在c#7中,您可以使用ValueTuples返回多个值。
伊曼·巴兰普

13

扩展“狗,猫”示例。使用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;
    }

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在方法中更改对象,而不必使用。


5
仅当未创建并返回新对象时,此方法才有效。创建新对象时,对旧对象的引用将丢失。
etsuba

8
这是错误的-请尝试以下操作:添加someObject = nullBar结束执行。您的代码可以正常运行,因为Bar对实例的only 引用已为空。现在更改Bar为,Bar(ref MyClass someObject)然后再次执行-您将得到一个NullReferenceException因为Foo对实例的引用也已为空。
基思2010年

8

refout行为类似,除了以下区别。

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


6

贝克

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

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

string s = "Able";

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

s = "Baker";

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

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

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

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

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


1
究竟。因此,严格地说“自从您传递了引用类型(一个类)以来,就无需使用ref”,这并不是真的。
Paul Mitchell

从理论上讲,这样做是正确的,因为他写了“以便可以修改”,这在字符串上是不可能的。但是由于对象是不变的,因此“ ref”和“ out”对于引用类型也非常有用!(.Net包含很多不可变的类!)
mmmmmmmm

你是对的。我没有想到像字符串之类的不可变对象,因为大多数对象都是可变的。
Albic

1
好的,可以肯定的是,这在LQP中是一个令人困惑的答案。它没有什么问题,只是它似乎是对另一条评论的长期而彻底的回应(因为原始问题在其修订本中均未提及Able和Baker),就好像这是一个论坛一样。我想那还没有真正解决。
内森·塔吉

6

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


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


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

      }
   }
}

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

1
您可以内联声明一个变量:out double Half_nbr
塞巴斯蒂安·霍夫曼'18

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处创建一个列表对象,并用此值替换#04中的#FF(或在此方法中取消引用原始List对象并指向新的List对象)

#00中的地址不变,并保留对#FF的引用(或myList不干扰原始指针)。


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

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


3

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

  • 不需要 ref或者out,如果你要做的就是修改的东西里面MyClass是在参数传递的实例someClass

    • 调用方法会看到这样的变化someClass.Message = "Hello World"是否使用refout或没有
    • 写入someClass = new MyClass()内部仅myFunction(someClass)交换someClassmyFunction方法范围内看到的对象。调用方法仍然知道MyClass它创建并传递给您的方法的原始实例
  • 需要 ref或者out如果您计划换someClass出一个新对象,并希望调用方法看到您的更改

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

存在其他程序员

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

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

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

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

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


Microsoft使用.TryParse数字类型上的方法执行此操作:

int i = 98234957;
bool success = int.TryParse("123", out i);

通过在out他们在这里积极声明时标记该参数,“我们肯定会将您精心制作的98234957的值更改为其他内容”

当然,对于诸如解析值类型之类的东西,它们还一定要这样做,因为如果不允许使用parse方法将值类型换成其他东西,它就不会很好地工作。您要创建的库:

public void PoorlyNamedMethod(out SomeClass x)

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

SomeClass x = SpendHoursMakingMeAPerfectSomeClass();
//now give it to the library
PoorlyNamedMethod(out x);

嗯,那是浪费时间,要花所有的时间来完成这个完美的课堂。它肯定会被扔掉,并被PoorlyNamedMethod取代


3

对于那些看起来简洁的答案。

这两个refout关键字用于通过逐reference


ref关键字的变量必须具有值,或者必须引用对象或null 传递对象之前


不像ref,可变out关键字必须有一个值,或者必须指一个对象或null 之后其传递以及没有必要有一个值或引用一个对象之前流逝。


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:传递了StringListnamed 的副本LiStri。在方法开始时,此副本引用原始列表,因此可用于修改此列表。稍后在方法内LiStri引用另一个List<string>不会影响原始列表的对象。

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

  • CiaoMondoLiStriOut是的别名,ListStringOut必须初始化。

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


1

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

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

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


1

下面我展示了同时使用Refout的示例。现在,将清除有关ref和out的所有信息。

在下面提到的示例中,当我评论/// myRefObj = new myClass {Name =“ ref外部调用!”“}; 行,将显示一条错误消息“使用未分配的局部变量'myRefObj'”,但out中没有此类错误。

在哪里使用Ref:当我们调用带有in参数的过程时,将使用相同的参数存储该proc的输出。

在哪里使用Out:当我们调用没有in参数且没有参数的过程时,将使用相同的参数从该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; }
} 

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


1

引用: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时不会区分这两者)。


0

从接收参数的方法的角度来看,ref和之间的区别out是C#要求方法必须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编写的方法时可能会发生。


0

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


-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上工作。


6
尽管这里的所有内容几乎都是正确的,但并没有真正解释按引用还是按值列出的价值之间的区别。最好的一半解释了引用和值/不可变类型之间的区别。
康拉德·弗里克斯

如果您想用该代码编写cat,请将该对象与“ ref”键一起传递,如下所示:public static void Bar(ref MyClass someObject),Bar(ref myObject);
Daniel Botero Correa

-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的原因,否则就不会传递引用。

据我所知,您仅需要对结构体/值类型和字符串本身进行引用,因为字符串是一种伪装成引用类型但不是值类型的引用类型。

我可能是完全错误的,我是新来的。


5
欢迎来到Stack Overflow,Edwin。据我所知,字符串是通过引用传递的,就像其他任何对象一样。您可能会感到困惑,因为字符串是不可变的对象,因此通过引用传递它们并不是很明显。想象一下,该字符串有一个称为的方法Capitalize(),该方法会将字符串的内容更改为大写字母。如果再换成您的线路a = "testing";a.Capitalize();,那么你的输出将是“HELLO”,而不是“你好”。不变类型的优点之一是您可以传递引用,而不必担心其他代码会更改值。
唐·柯比

2
一个类型可以提供三种基本的语义类型:可变引用语义,可变值语义和不可变语义。考虑具有字段或属性m的类型T的变量x和y,并假定x复制到y。如果T具有参考语义,则ym将观察到xm的变化。如果T具有值语义,则可以在不影响ym的情况下更改xm。如果T具有不变的语义,则xm和ym都不会改变。不变的语义可以通过引用或值对象来模拟。字符串是不可变的引用对象。
2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.