'ref'和'out'关键字有什么区别?
我创build一个函数,我需要传递一个对象,以便它可以被修改的function。 有什么区别:
public void myFunction(ref MyClass someClass)
和
public void myFunction(out MyClass someClass)
我应该使用哪个,为什么?
ref
告诉编译器在进入函数之前对象被初始化,而out
告诉编译器该对象将在函数内被初始化。
所以当ref
是双向时, out
是唯一的。
ref
修饰符意味着:
- 该值已经设置好了
- 该方法可以读取和修改它。
out
修饰符意味着:
- 该值没有设置, 直到设置后才能被该方法读取。
- 该方法必须在返回之前设置它。
让我们说,唐在彼得的小隔间出现关于TPS报告的备忘录。
如果Dom是一个参考论据,他会有一份备忘录的印刷本。
如果Dom是一个争论的话,他会让彼得打印一份备忘录的新副本,让他拿走。
我会试着解释一下:
我想我们理解价值types是如何运作的? 值types是(int,long,struct等)。 当你发送一个没有ref命令的函数时,它会复制数据 。 您在该函数中对该数据所做的任何操作只会影响副本,而不会影响原始数据。 ref命令发送ACTUAL数据,任何更改都将影响该函数之外的数据。
确定一下混淆部分,参考types:
让我们创build一个引用types:
List<string> someobject = new List<string>()
当你更新某个对象时 ,会创build两个部分:
- 为某个对象保存数据的内存块
- 对该数据块的引用(指针)。
现在,当你发送一个对象到一个没有引用的方法时,它复制引用指针,而不是数据。 所以你现在有这个:
(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”用于从方法中获取值。
- 您不需要在调用函数中初始化该值。
- 您必须在调用函数中分配值,否则编译器将报告错误。
参考:
在C#中,当你传递一个值types,如int,float,double等作为参数给方法参数时,它是通过值传递的。 因此,如果修改参数值,则不会影响方法调用中的参数。 但是如果用“ref”关键字标记参数,它将反映在实际variables中。
- 在调用函数之前,您需要初始化variables。
- 在方法中为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
如果你想改变你的方法内的对象。
除了以下的差异之外, ref
和out
行为是相似的
-
ref
variables必须在使用前初始化。out
variables可以不用赋值就可以使用 -
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和其他的例子来说明这一点。 我没有介绍最佳实践,只是为了理解差异。
他们几乎是一样的 – 唯一的区别是你作为一个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
下面我已经展示了一个使用Ref和Out的例子。 现在,你们都将被清除关于裁判和出局。
在下面提到的例子中,当我评论// 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; } }
从接收参数的方法来看, ref
和out
之间的区别在于C#要求方法在返回之前必须写入每个out
参数,除了将其作为out
parameter passing外,不得对这样的参数做任何事情或写入,直到它已经作为一个out
parameter 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。
虽然我可能完全错了,但我是新的。