我正在创建一个函数,我需要在其中传递一个对象,以便该函数可以修改它。有什么区别:
public void myFunction(ref MyClass someClass)
和
public void myFunction(out MyClass someClass)
我应该使用哪个,为什么?
MyClass
将是一个 class
类型,即引用类型。在这种情况下,即使没有 ref
/out
关键字,您传递的对象也可以由 myFunction
修改。 myFunction
将接收一个指向 same 对象的 new 引用,并且它可以根据需要修改同一个对象。 ref
关键字的不同之处在于,myFunction
收到了对同一对象的 same 引用。仅当 myFunction
将引用更改为指向 另一个 对象时,这才是重要的。
ref
告诉编译器对象在进入函数之前被初始化,而 out
告诉编译器对象将在函数内部被初始化。
因此,虽然 ref
是双向的,但 out
是唯一的。
ref
修饰符意味着:
该值已设置并且该方法可以读取和修改它。
out
修饰符意味着:
该值未设置,并且在设置之前无法由该方法读取。该方法必须在返回之前设置它。
out
,如果它在方法被调用之前已被初始化,是否可以在该方法设置之前在方法内读取它?我的意思是,被调用的方法可以读取调用方法作为参数传递给它的内容吗?
假设 Dom 出现在 Peter 的隔间,内容是关于 TPS 报告的备忘录。
如果 Dom 是一个 ref 参数,他将有一份备忘录的打印副本。
如果 Dom 是个吵架者,他会让 Peter 打印一份新的备忘录,让他随身携带。
我将尝试解释:
我认为我们了解值类型如何正确工作?值类型是(int、long、struct 等)。当您将它们发送到没有 ref 命令的函数时,它会复制数据。您对函数中的数据所做的任何事情都只会影响副本,而不是原始数据。 ref 命令发送 ACTUAL 数据,任何更改都会影响函数外的数据。
好的,进入令人困惑的部分,引用类型:
让我们创建一个引用类型:
List<string> someobject = new List<string>()
当您新建某个对象时,会创建两个部分:
为某个对象保存数据的内存块。对该数据块的引用(指针)。
现在,当您将 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
ref
和 out
参数之间的区别。
out
关键字吗?
只要满足您的要求,您就应该优先使用 out
。
出去:
在 C# 中,一个方法只能返回一个值。如果你想返回多个值,你可以使用 out 关键字。 out 修饰符以引用返回的形式返回。最简单的答案是关键字“out”用于从方法中获取值。
您不需要在调用函数中初始化值。必须在被调用函数中赋值,否则编译器会报错。
参考:
在 C# 中,当您将 int、float、double 等值类型作为参数传递给方法参数时,它是按值传递的。因此,如果修改参数值,不会影响方法调用中的参数。但是如果你用“ref”关键字标记参数,它会反映在实际变量中。
您需要在调用函数之前初始化变量。不必为方法中的 ref 参数分配任何值。如果不改变值,有什么必要将其标记为“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 表示 ref 参数中的值已经设置,方法可以读取和修改它。使用 ref 关键字相当于说调用者负责初始化参数的值。
out 告诉编译器对象的初始化是函数的责任,函数必须分配给 out 参数。不允许将其保留为未分配状态。
https://www.codemaggot.com/ref-and-out-keywords/
ref
和 out
的行为相似,但有以下区别。
ref 变量必须在使用前初始化。 out 变量可以在没有赋值的情况下使用
out 参数必须被使用它的函数视为未分配的值。因此,我们可以在调用代码中使用初始化的 out 参数,但是当函数执行时该值会丢失。
对于那些通过示例学习的人(比如我),这里是 Anthony Kolesov is saying。
我已经创建了一些 ref、out 和其他的最小示例来说明这一点。我没有介绍最佳实践,只是举例来了解差异。
https://gist.github.com/2upmedia/6d98a57b68d849ee7091
由于您传递的是引用类型(一个类),因此无需使用 ref
,因为默认情况下只传递了对实际对象的 reference,因此您总是更改引用后面的对象.
例子:
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
。
someObject = null
添加到 Bar
结束执行。您的代码将运行良好,因为只有 Bar
对实例的引用被取消。现在将 Bar
更改为 Bar(ref MyClass someObject)
并再次执行 - 您将获得 NullReferenceException
,因为 Foo
对实例的引用也已为空。
“贝克”
那是因为第一个将您的字符串引用更改为指向“贝克”。更改引用是可能的,因为您通过 ref 关键字(=> 对字符串引用的引用)传递了它。第二个调用获取对字符串的引用的副本。
字符串起初看起来有些特别。但是字符串只是一个参考类,如果你定义
string s = "Able";
那么 s 是对包含文本“Able”的字符串类的引用!通过对同一变量的另一个赋值
s = "Baker";
不会更改原始字符串,只是创建一个新实例并让 s 指向该实例!
您可以使用以下小代码示例进行尝试:
string s = "Able";
string s2 = s;
s = "Baker";
Console.WriteLine(s2);
你能指望什么?您将得到的仍然是“Able”,因为您只是将 s 中的引用设置为另一个实例,而 s2 指向原始实例。
编辑:字符串也是不可变的,这意味着根本没有修改现有字符串实例的方法或属性(您可以尝试在文档中找到一个,但您不会找到任何 :-))。所有字符串操作方法都返回一个新的字符串实例! (这就是为什么在使用 StringBuilder 类时通常会获得更好的性能)
对于那些寻找简洁答案的人。
ref 和 out 关键字都用于传递引用。 ref 关键字的变量必须有一个值,或者必须在传递之前引用一个对象或 null。与 ref 不同,out 关键字的变量在传递后必须有值或必须引用对象或 null,并且在传递之前不需要有值或引用对象。
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
还可以让其他人知道您需要他们做什么以及您打算做什么他们提供的变量
如果您要做的只是修改在参数 someClass 中传递的 MyClass 实例中的内容,则不需要 ref 或 out。调用方法将看到类似 someClass.Message = "Hello World" 的变化,无论你使用 ref、out 还是什么都没有 在 myFunction(someClass) 中写入 someClass = new MyClass() 会换出 myFunction 方法范围内 someClass 看到的对象只要。调用方法仍然知道它创建并传递给您的方法的原始 MyClass 实例
调用方法将看到类似 someClass.Message = "Hello World" 的变化,无论您使用 ref、out 还是什么都没有
在 myFunction(someClass) 中写入 someClass = new MyClass() 仅在 myFunction 方法的范围内换出 someClass 看到的对象。调用方法仍然知道它创建并传递给您的方法的原始 MyClass 实例
如果您计划将 someClass 换成一个全新的对象并希望调用方法看到您的更改,则需要 ref 或 out 在 myFunction(out someClass) 中写入 someClass = new MyClass() 会更改调用 myFunction 的方法看到的对象
在 myFunction(out someClass) 中写入 someClass = new MyClass() 会更改调用 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 值更改为其他值”
当然,对于解析值类型之类的事情,他们有点不得不这样做,因为如果不允许解析方法将值类型换成其他东西,它就不会很好地工作。但是想象一下,有些方法中有一些虚构的方法您正在创建的库:
public void PoorlyNamedMethod(out SomeClass x)
您可以看到它是一个 out
,因此您可以知道,如果您花费数小时处理数字,则可以创建完美的 SomeClass:
SomeClass x = SpendHoursMakingMeAPerfectSomeClass();
//now give it to the library
PoorlyNamedMethod(out x);
好吧,那是浪费时间,花所有这些时间来制作完美的课程。它肯定会被扔掉,取而代之的是PoorlyNamedMethod
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);
out double 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) 然后运行时将对象的起始地址#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) 在堆上创建对象并将其起始地址存储在参数变量中。不会抛出未初始化的错误,并且之前存储的任何引用都将丢失。
为了说明许多出色的解释,我开发了以下控制台应用程序:
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
HalloWelt:LiStriRef 是已经初始化的 ListStringRef 的别名。传递的 List
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 在运行时的处理方式不同,但在编译时它们的处理方式相同(CLR 在为 ref 和 out 创建 IL 时不会区分两者)。
下面我展示了一个同时使用 Ref 和 out 的示例。现在,你们都将被清除关于 ref 和 out 的信息。
在下面提到的示例中,当我评论 //myRefObj = new myClass { Name = "ref outside called!! " };行,会得到一个错误,说“使用未分配的局部变量'myRefObj'”,但输出中没有这样的错误。
在哪里使用 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 通过引用传递,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
从接收参数的方法的角度来看,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
保持不变。
请注意,用 VB.NET 编写的构造函数与 C# 中的构造函数不同,它不会假设被调用的方法是否会修改任何 out
参数,并且会无条件地清除所有字段。上面提到的奇怪行为不会发生在完全用 VB 或完全用 C# 编写的代码中,但是当用 C# 编写的代码调用用 VB.NET 编写的方法时可能会发生。
如果要将参数作为 ref 传递,则应在将参数传递给函数之前对其进行初始化,否则编译器本身将显示错误。但在 out 参数的情况下,您无需在将对象参数传递给之前初始化它方法。您可以在调用方法本身中初始化对象。
请注意,在函数内部传递的引用参数是直接处理的。
例如,
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 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 结构/值类型和字符串本身,因为字符串是一种假装它是但不是值类型的引用类型。
不过,我在这里可能完全错了,我是新手。
Capitalize()
的方法,可以将字符串的内容更改为大写字母。如果您随后将行 a = "testing";
替换为 a.Capitalize();
,那么您的输出将是“HELLO”,而不是“Hello”。不可变类型的优点之一是您可以传递引用,而不必担心其他代码会更改值。