[英]What's the difference between the 'ref' and 'out' keywords?
我正在创建一个 function,我需要传递一个 object,以便它可以被 function 修改。有什么区别:
public void myFunction(ref MyClass someClass)
和
public void myFunction(out MyClass someClass)
我应该使用哪个,为什么?
ref
告诉编译器对象在进入函数之前被初始化,而out
告诉编译器对象将在函数内部初始化。
因此,虽然ref
是双向的,但out
是仅输出的。
ref
修饰符意味着:
out
修饰符意味着:
假设 Dom 出现在 Peter 的小隔间,关于 TPS 报告的备忘录。
如果 Dom 是一个 ref 参数,他会有一份备忘录的打印副本。
如果 Dom 是个外人,他会让 Peter 打印一份备忘录的新副本,让他随身携带。
我将尝试解释一下:
我想我们理解值类型是如何工作的吗? 值类型是(int、long、struct 等)。 当您将它们发送到没有 ref 命令的函数时,它会复制数据。 您对函数中的数据所做的任何操作只会影响副本,而不是原始数据。 ref 命令发送 ACTUAL 数据,任何更改都会影响函数外的数据。
好的,令人困惑的部分,引用类型:
让我们创建一个引用类型:
List<string> someobject = new List<string>()
当您新建someobject 时,会创建两个部分:
现在,当你在someobject送入方法没有裁判它复制引用指针,而不是数据。 所以你现在有这个:
(outside method) reference1 => someobject
(inside method) reference2 => someobject
两个引用指向同一个对象。 如果您在使用给定2修改someobject属性会影响通过参考量都指向同一数据。
(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)当您将方法内的引用设为 null 时,它会将方法外的引用设为 null。
(inside method) reference1 = null;
(outside method) reference1 == null; //true
2) 您现在可以将引用指向一个完全不同的数据位置,函数外部的引用现在将指向新的数据位置。
(inside method) reference1 = new List<string>();
(outside method) reference1.Count == 0; //this is true
只要满足您的要求,您就应该优先使用out
。
在 C# 中,一个方法只能返回一个值。 如果您想返回多个值,可以使用 out 关键字。 out 修饰符作为返回引用返回。 最简单的答案是关键字“out”用于从方法中获取值。
在 C# 中,当你将一个值类型如 int、float、double 等作为参数传递给方法参数时,它是按值传递的。 因此,如果修改参数值,不会影响方法调用中的参数。 但是如果你用“ref”关键字标记参数,它将反映在实际变量中。
扩展狗,猫的例子。 带有 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;
}
ref
和out
行为相似,但有以下区别。
ref
变量必须在使用前初始化。 out
变量无需赋值即可使用out
参数必须被使用它的函数视为未分配的值。 因此,我们可以在调用代码中使用已初始化的out
参数,但该值会在函数执行时丢失。对于那些通过实例学习的人(像我一样),这就是Anthony Kolesov 所说的。
我创建了一些最小的 ref、out 和其他示例来说明这一点。 我不是在介绍最佳实践,只是为了理解差异的例子。
ref表示已经设置了 ref 参数中的值,该方法可以读取和修改它。 使用 ref 关键字等同于说调用者负责初始化参数的值。
out告诉编译器对象的初始化是函数的职责,函数必须分配给 out 参数。 不允许不分配。
由于您正在传递引用类型(类),因此无需使用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 关键字(=> 对字符串引用的引用)传递了它。 第二个调用获取字符串引用的副本。
字符串起初看起来有些特别。 但字符串只是一个参考类,如果你定义
string s = "Able";
那么 s 是对包含文本“Able”的字符串类的引用! 通过对同一变量的另一个赋值
s = "Baker";
不会更改原始字符串,而只是创建一个新实例并让 s 指向该实例!
您可以使用以下小代码示例进行尝试:
string s = "Able";
string s2 = s;
s = "Baker";
Console.WriteLine(s2);
你期待什么? 您将得到的仍然是“Able”,因为您只是将 s 中的引用设置为另一个实例,而 s2 指向原始实例。
编辑:字符串也是不可变的,这意味着根本没有修改现有字符串实例的方法或属性(您可以尝试在文档中找到一个,但您不会找到任何 :-) )。 所有字符串操作方法都返回一个新的字符串实例! (这就是为什么您在使用 StringBuilder 类时通常会获得更好的性能)
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();
}
}
}
对于那些寻找简洁答案的人。
ref
和out
关键字都用于传递reference
。
ref
关键字的变量在传递之前必须有一个值或必须引用一个对象或null
。
与
ref
不同,out
关键字的变量在传递后必须有值或必须引用对象或null
,并且在传递前不需要有值或引用对象。
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) 我们创建调用方法Main()
(2) 它创建一个 List 对象(它是一个引用类型对象)并将其存储在变量myList
。
public sealed class Program
{
public static Main()
{
List<int> myList = new List<int>();
在运行时:
(3) 运行时在#00 的堆栈上分配内存,足够宽以存储地址(#00 = myList
,因为变量名实际上只是内存位置的别名)
(4) 运行时在内存位置#FF 的堆上创建一个列表对象(所有这些地址都是为了举例)
(5) Runtime 然后将对象的起始地址 #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) Runtime在内存位置#004的堆上创建一个list对象,并将#04中的#FF替换为这个值(或者在这个方法中解引用原来的List对象,指向新的List对象)
#00 中的地址不会改变并保留对myList
的引用(或原始myList
指针不会受到干扰)。
ref关键字是一个编译器指令,用于跳过 (8) 和 (9) 的运行时代码的生成,这意味着不会为方法参数分配堆。 它将使用原始#00 指针对#FF 处的对象进行操作。 如果原始指针未初始化,运行时将停止抱怨它无法继续,因为变量未初始化
out关键字是一个编译器指令,它与 ref 几乎相同,只是在 (9) 和 (10) 处稍作修改。 编译器期望参数未初始化,并将继续 (8)、(4) 和 (5) 在堆上创建一个对象并将其起始地址存储在参数变量中。 不会抛出未初始化的错误,并且任何先前存储的引用都将丢失。
除了允许您将其他人的变量重新分配给类的不同实例、返回多个值等,使用ref
或out
可以让其他人知道您需要从他们那里获得什么以及您打算对他们提供的变量做什么
你并不需要ref
或out
,如果你要做的是里面修改内容MyClass
是在参数传递实例someClass
。
ref
、 out
还是什么都不使用,调用方法都会看到类似someClass.Message = "Hello World"
变化myFunction(someClass)
写入someClass = new MyClass()
someClass
在myFunction
方法的范围内换出someClass
看到的对象。 调用方法仍然知道它创建并传递给您的方法的原始MyClass
实例如果您计划将someClass
交换为一个全新的对象并希望调用方法看到您的更改out
则需要ref
或out
myFunction(out someClass)
写入someClass = new MyClass()
改变调用myFunction
的方法看到的对象他们想知道您将如何处理他们的数据。 想象一下,您正在编写一个可供数百万开发人员使用的库。 您希望他们在调用您的方法时知道您将如何处理他们的变量
使用ref
声明“当您调用我的方法时传递分配给某个值的变量。请注意,我可能会在我的方法过程中将其完全更改为其他内容。不要期望您的变量指向旧的当我完成时反对”
使用out
声明“将占位符变量传递给我的方法。它是否有值无关紧要;编译器会强迫我将其分配给一个新值。我绝对保证所指向的对象在调用我的方法之前,您的变量会在我完成时有所不同
in
修饰符这可以防止该方法将传入的实例换出不同的实例。 把它想象成对那些数以百万计的开发人员说“把你的原始变量引用传给我,我保证不会把你精心制作的数据换成别的东西”。 in
有一些特殊性,在某些情况下,例如可能需要隐式转换才能使您的 short 与in int
兼容,编译器将临时制作一个 int,将您的 short 扩大到它,通过引用传递并完成。 它可以这样做,因为你已经声明你不会惹它。
微软使用数字类型的.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
为了说明许多出色的解释,我开发了以下控制台应用程序:
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
:传递名为LiStri
的StringList
副本。 在方法开始时,此副本引用原始列表,因此可用于修改此列表。 后来LiStri
在方法中引用了另一个List<string>
对象,该对象不会影响原始列表。
HalloWelt
: LiStriRef
是已经初始化的ListStringRef
的别名。 传递的List<string>
对象用于初始化一个新对象,因此ref
是必要的。
CiaoMondo
: LiStriOut
是一个别名ListStringOut
,必须初始化。
所以,如果一个方法只是修改了传递变量引用的对象,编译器不会让你使用out
并且你不应该使用ref
因为它不会混淆编译器而是代码的阅读者。 如果该方法将使传递的参数引用另一个对象,则将ref
用于已初始化的对象,而out
用于必须为传递的参数初始化新对象的方法。 除此之外, ref
和out
行为相同。
它们几乎相同 - 唯一的区别是您作为输出参数传递的变量不需要初始化,并且使用 ref 参数的方法必须将其设置为某个值。
int x; Foo(out x); // OK
int y; Foo(ref y); // Error
Ref 参数用于可能被修改的数据,out 参数用于作为函数(例如 int.TryParse)的附加输出的数据,这些数据已经将返回值用于某事。
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 不能同时用于方法重载。 但是, ref 和 out 在运行时的处理方式不同,但在编译时的处理方式相同(CLR 在为 ref 和 out 创建 IL 时不区分两者)。
下面我展示了一个同时使用Ref和out的例子。 现在,你们都将了解 ref 和 out。
在下面提到的例子中,当我评论//myRefObj = new myClass { Name = "ref external called!! " }; 行,将收到一条错误消息,指出“使用未分配的局部变量 '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; }
}
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
从接收参数的方法的角度来看, ref
和out
的区别在于C#要求方法在返回之前必须写入每一个out
参数,并且不能对这样的参数做任何事情,除了将其作为out
参数传递或写入它,直到它作为out
参数传递给另一个方法或直接写入。 请注意,其他一些语言没有强加这样的要求; 在 C# 中使用out
参数声明的虚拟或接口方法可能会被另一种语言覆盖,该语言不对此类参数施加任何特殊限制。
从调用者的角度来看,C# 在许多情况下会假设调用带有out
参数的方法会导致传递的变量在没有先被读取的情况下被写入。 在调用用其他语言编写的方法时,这种假设可能不正确。 例如:
struct MyStruct
{
...
myStruct(IDictionary<int, MyStruct> d)
{
d.TryGetValue(23, out this);
}
}
如果myDictionary
标识了用 C# 以外的语言编写的IDictionary<TKey,TValue>
实现,即使MyStruct s = new MyStruct(myDictionary);
看起来像一个任务,它可能离开s
不变。
请注意,与 C# 中的构造函数不同,用 VB.NET 编写的构造函数不假设被调用的方法是否会修改任何out
参数,并无条件清除所有字段。 上面提到的奇怪行为不会发生在完全用 VB 或完全用 C# 编写的代码中,但是当用 C# 编写的代码调用用 VB.NET 编写的方法时可能会发生。
如果要将参数作为 ref 传递,则应在将参数传递给函数之前对其进行初始化,否则编译器本身将显示错误。方法。您可以在调用方法本身中初始化对象。
我想举例说明两个主要区别:
ref
和out
通过引用传递,母鸡; 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
迟到的答案,但想到了发布。 可能比其他答案更清楚。
参考关键词:
ref 是一个关键字,用于通过引用传递任何值(请参阅编程中的按值调用和按引用调用以获取更多知识)。 简而言之,您声明并初始化一个值,例如让我们说int age = 5;
所以这个age保存在memory中占了4个字节的位置。 现在,如果您使用 ref 将此 age 变量传递给另一个方法(这意味着通过引用而不是通过值传递它们),那么编译器将只传递该变量的引用,或者明确地说,变量所在位置的 memory 地址被存储并且被调用的方法接收到这个地址并直接访问该地址中的数据。 所以很明显,对该数据的任何更改也会发生在调用方法中存在的变量上。
示例:我将我的 stackoverflow 帐户的密码告诉他,他可以做任何他想做的事情,他可以提问或回答。 问题是,他所做的任何更改都会直接影响我的帐户。
输出关键词:
out 和 in 类似于它们都传递变量的引用。 现在我们知道两者都需要传递变量的引用,很明显memory中一定有一个地方存在,用来存放变量的字节。 但在out的情况下不会初始化。 因为要求是,被调用的方法必须初始化值并返回它。
示例:我将 stackoverflow 站点地址发送给我的朋友,并请他为我创建一个帐户并返回凭据。
在关键字:
现在 in 关键字与 ref 关键字的作用完全相同,只有一个条件,即不得修改作为引用传递的值。
示例:我提供了我的 stackoverflow 帐户的密码,但告诉他除了阅读或浏览网站外不要做任何事情。 不问任何问题,没有答案,没有投票什么都没有..
MSDN 参考资料:
希望以上是清楚的。
请注意,直接处理函数内部传递的引用参数。
例如,
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;
}
这将写入 Dog,而不是 Cat。 因此你应该直接在 someObject 上工作。
我可能不太擅长这个,但是字符串(即使它们在技术上是引用类型并且存在于堆中)肯定是按值传递的,而不是引用?
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 ,因为字符串是一种引用类型,它假装它是但不是值类型。
不过,我在这里可能完全错了,我是新手。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.