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

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

public void myFunction(ref MyClass someClass) 

 public void myFunction(out MyClass someClass) 

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

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

所以当ref是双向时, out是唯一的。

ref修饰符意味着:

  1. 该值已经设置好了
  2. 该方法可以读取和修改它。

out修饰符意味着:

  1. 该值没有设置, 直到设置后才能被该方法读取。
  2. 该方法必须在返回之前设置它。

让我们说,唐在彼得的小隔间出现关于TPS报告的备忘录。

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

如果Dom是一个争论的话,他会让彼得打印一份备忘录的新副本,让他拿走。

我会试着解释一下:

我想我们理解价值types是如何运作的? 值types是(int,long,struct等)。 当你发送一个没有ref命令的函数时,它会复制数据 。 您在该函数中对该数据所做的任何操作只会影响副本,而不会影响原始数据。 ref命令发送ACTUAL数据,任何更改都将影响该函数之外的数据。

确定一下混淆部分,参考types:

让我们创build一个引用types:

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

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

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

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

 (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 

现在当你通过一个方法发送一个对象时会发生什么? 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的第二个方法更改调用者引用的对象。 因此“猫”!

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

出:

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

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

参考:

在C#中,当你传递一个值types,如int,float,double等作为参数给方法参数时,它是通过值传递的。 因此,如果修改参数值,则不会影响方法调用中的参数。 但是如果用“ref”关键字标记参数,它将反映在实际variables中。

  1. 在调用函数之前,您需要初始化variables。
  2. 在方法中为ref参数赋值并不是强制的。 如果你不改变这个值,有什么需要把它标记为“ref”?

由于您传入引用types(类),因此不需要使用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如果你想改变你的方法内的对象。

除了以下的差异之外, refout行为是相似的

  • refvariables必须在使用前初始化。 outvariables可以不用赋值就可以使用
  • out参数必须被使用它的函数视为未分配的值。 所以我们可以在调用代码中使用初始化out参数,但是执行函数的时候这个值会丢失。

“贝克”

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

string起初看起来有些特别。 但string只是一个引用类,如果你定义

 string s = "Able"; 

那么s是一个包含文本“Able”的string类的引用! 通过另一个赋值给同一个variables

 s = "Baker"; 

不会更改原始string,只是创build一个新的实例,让我们指向该实例!

你可以用下面的代码示例来尝试:

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

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

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

输出:返回语句可用于从函数返回一个值。 但是,使用输出参数,可以从函数返回两个值。 输出参数与参考参数相似,不同之处在于它们将数据从方法中传出而不是传入数据。

以下示例说明了这一点:

 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:引用参数是对variables的内存位置的引用。 当通过引用传递参数时,与值参数不同,不会为这些参数创build新的存储位置。 参考参数表示与提供给方法的实际参数相同的存储位置。

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

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


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

在这里阅读。

ref和out的工作就像通过引用和传递指针一样在C ++中传递。

对于参考,论证必须声明和初始化。

对于出局,论证必须宣布,但可能会或可能不会被初始化

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

对于像我这样学习的人来说,这就是安东尼·科列索夫(Anthony Kolesov)所说的话 。

我已经创build了一些ref,out和其他的例子来说明这一点。 我没有介绍最佳实践,只是为了理解差异。

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

他们几乎是一样的 – 唯一的区别是你作为一个outparameter passing的variables不需要被初始化,并且使用ref参数的方法必须将它设置为某个东西。

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

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

  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”的时候,它在两种情况下都起作用,不pipe你是初始化那个int / string还是不要,但是你必须在那个函数中初始化那个int / string

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

在下面提到的例子中,当我评论// myRefObj = new myClass {Name =“ref outside called !!”}; 行,会得到一个错误,说“使用未分配的局部variablesmyRefObj” ,但没有这样的错误出来

在哪里使用参考 :当我们正在调用一个in参数的过程和相同的参数将被用来存储该proc的输出。

在哪里使用Out:当我们正在调用一个没有参数的过程时,将使用相同的参数来返回该过程中的值。 还要注意输出

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

从接收参数的方法来看, refout之间的区别在于C#要求方法在返回之前必须写入每个out参数,除了将其作为outparameter passing外,不得对这样的参数做任何事情或写入,直到它已经作为一个outparameter passing给另一个方法或直接写入。 请注意,其他一些语言不会强加这样的要求; 在C#中用out参数声明的虚拟或接口方法可能会被另一种语言覆盖,这种语言不会对这些参数施加任何特殊的限制。

从调用者的angular度来看,在许多情况下,C#在调用带有out参数的方法时会导致写入的variables没有先被读取。 调用以其他语言编写的方法时,此假设可能不正确。 例如:

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

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

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

参考: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之间不会区分这两者)。

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

在这里ref是很好的解释,但请记住ref可能会被调用未初始化的对象。

看到这个例子 – 注意s1的范围

 public class Class1 { // uninitialized 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) { } } 

创作时间:

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

(2)它创build一个List对象(它是一个引用types对象)并将其存储在variablesmyList

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

运行期间:

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

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

(5)运行时间然后将对象的开始地址#FF存储在#00(或者以字的forms,将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)运行时启动被调用方法的调用例程,并作为其一部分检查参数的types。

(8)在find引用types后,它在#04的堆栈上分配一个内存,以便为参数variablesmyParamList

(9)然后将值#FF存储在其中。

(10)运行时在内存位置#004的堆上创build一个列表对象,并用这个值replace#04中的#FF(或者取消引用原来的List对象,并在这个方法中指向新的List对象)

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


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

out关键字是一个编译器指令,它几乎与(9)和(10)稍微修改的ref相同。 编译器期望这个参数是未初始化的,并将继续(8),(4)和(5)在堆上创build一个对象并将其起始地址存储在参数variables中。 没有未初始化的错误将被抛出,任何以前的参考存储将会丢失。

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

例如,

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

我可能不太擅长这个,但是确实是string(尽pipe它们在技术上是引用types,并且在堆上)是通过值传递的,而不是引用?

  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,如果你想要改变的function范围外存在他们,你没有通过一个参考,否则。

据我所知你只需要ref /结构types和string本身,因为string是一个引用types,假装它是,但不是一个值types。

虽然我可能完全错了,但我是新的。