我正在创建一个需要传递对象的函数,以便可以通过该函数对其进行修改。之间有什么区别?
public void myFunction(ref MyClass someClass)
和
public void myFunction(out MyClass someClass)
我应该使用哪个?为什么?
我正在创建一个需要传递对象的函数,以便可以通过该函数对其进行修改。之间有什么区别?
public void myFunction(ref MyClass someClass)
和
public void myFunction(out MyClass someClass)
我应该使用哪个?为什么?
Answers:
ref
告诉编译器对象在进入函数之前已初始化,而out
告诉编译器对象将在函数内进行初始化。
因此,虽然ref
是双向的,out
却是唯一的。
该ref
修改意味着:
该out
修改意味着:
out
,如果在调用该方法之前已对其进行了初始化,则可以在该方法设置之前在方法内完全读取它吗?我的意思是,被调用方法可以读取作为参数传递给它的调用方法吗?
假设Dom出现在Peter的小隔间中,展示了有关TPS报告的备忘录。
如果Dom是裁判的论点,他将拥有备忘录的印刷版。
如果Dom提出异议,他会让Peter打印一份新的备忘录供他随身携带。
我将尝试解释一下:
我认为我们了解值类型的运作方式正确吗?值类型为(int,long,struct等)。当您将它们发送给没有ref命令的函数时,它将复制数据。您对函数中的数据所做的任何操作只会影响副本,而不会影响原始副本。ref命令发送ACTUAL数据,任何更改都会影响该功能之外的数据。
好了,让我们困惑的部分是引用类型:
让我们创建一个引用类型:
List<string> someobject = new List<string>()
当您新建someobject时,将创建两个部分:
现在,当你在发送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
和out
参数之间的区别。
out
关键字解释一样吗?
out
只要满足您的需求,就应该优先使用。
在C#中,方法只能返回一个值。如果您想返回多个值,则可以使用out关键字。out修饰符return作为按引用返回。最简单的答案是关键字“ 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
因为默认情况下仅传递对实际对象的引用,因此您始终在引用后面更改对象。
例:
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
对实例的only 引用已为空。现在更改Bar
为,Bar(ref MyClass someObject)
然后再次执行-您将得到一个NullReferenceException
因为Foo
对实例的引用也已为空。
对于那些通过榜样学习的人(例如我),这就是安东尼·科列索夫(Anthony Kolesov)所说的。
我创建了一些最小的ref,out示例,并通过其他示例来说明这一点。我没有介绍最佳实践,仅是了解差异的示例。
贝克
那是因为第一个将您的字符串引用更改为指向“贝克”。可以更改引用,因为您是通过ref关键字(=>对字符串的引用进行引用)传递的。第二次调用获取对该字符串的引用的副本。
起初字符串看起来有些特殊。但是字符串只是一个参考类,如果您定义
string s = "Able";
那么s是对包含文本“ Able”的字符串类的引用!通过另一个对相同变量的赋值
s = "Baker";
不会更改原始字符串,而只是创建一个新实例并让我们指向该实例!
您可以使用下面的小代码示例进行尝试:
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的工作就像在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处创建一个列表对象,并用此值替换#04中的#FF(或在此方法中取消引用原始List对象并指向新的List对象)
#00中的地址不变,并保留对#FF的引用(或myList
不干扰原始指针)。
该REF关键字是一个编译器指令跳过的运行时代码生成用于(8)和(9),这意味着将有方法的参数没有堆分配。它将使用原始的#00指针对#FF处的对象进行操作。如果未初始化原始指针,则运行时将因无法初始化变量而抱怨无法继续
该出关键字是一个编译器指令,它几乎是相同的,在与(9)和(10)的轻微修改裁判。编译器期望参数未初始化,并继续进行(8),(4)和(5),以在堆上创建对象并将其起始地址存储在参数变量中。不会引发未初始化的错误,并且任何先前存储的引用都将丢失。
以及允许您将其他人的变量重新分配给类的其他实例,返回多个值等,使用ref
或out
让其他人知道您从他们那里需要什么,以及他们打算如何使用他们提供的变量
你不需要 ref
或者out
,如果你要做的就是修改的东西里面的MyClass
是在参数传递的实例someClass
。
someClass.Message = "Hello World"
是否使用ref
,out
或没有someClass = new MyClass()
内部仅myFunction(someClass)
交换someClass
在myFunction
方法范围内看到的对象。调用方法仍然知道MyClass
它创建并传递给您的方法的原始实例您需要 ref
或者out
如果您计划换someClass
出一个新对象,并希望调用方法看到您的更改
someClass = new MyClass()
在内部编写会myFunction(out someClass)
更改通过调用方法看到的对象myFunction
他们想知道您将如何处理他们的数据。想象一下,您正在编写一个可供数百万开发人员使用的库。您希望他们在调用方法时知道对变量的处理方式
使用ref
语句为“在调用我的方法时传递分配给某个值的变量。请注意,在我使用方法的过程中,我可能会完全将其更改为其他值。不要指望您的变量指向旧对象。当我完成后”
使用时out
会声明“将占位符变量传递给我的方法。它是否有值无关紧要;编译器将强制我将其分配为新值。我绝对保证您所指向的对象在调用我的方法之前的变量,在我完成操作时会有所不同
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取代
为了说明许多出色的解释,我开发了以下控制台应用程序:
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
:传递了StringList
named 的副本LiStri
。在方法开始时,此副本引用原始列表,因此可用于修改此列表。稍后在方法内LiStri
引用另一个List<string>
不会影响原始列表的对象。
HalloWelt
:LiStriRef
是已经初始化的别名
ListStringRef
。传递的List<string>
对象用于初始化一个新对象,因此ref
是必需的。
CiaoMondo
:LiStriOut
是的别名,ListStringOut
必须初始化。
因此,如果一个方法只是修改了传递的变量所引用的对象,则编译器将不允许您使用out
,也不应使用,ref
因为它会使编译器而不是代码阅读者感到困惑。如果该方法将使传递的参数引用另一个对象,则将其ref
用于已初始化的对象以及out
必须为传递的参数初始化新对象的方法。除此之外,ref
和out
相同的行为。
下面我展示了同时使用Ref和out的示例。现在,将清除有关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; }
}
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关键字用于传递参数作为引用。这意味着,在方法中更改该参数的值时,它将反映在调用方法中。使用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
是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编写的方法时可能会发生。
如果您想将参数作为ref传递,则应在将参数传递给函数之前对其进行初始化,否则编译器本身将显示错误。您可以在调用方法本身中初始化对象。
请注意,直接在函数内部传递的参考参数是正确的。
例如,
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的原因,否则就不会传递引用。
据我所知,您仅需要对结构体/值类型和字符串本身进行引用,因为字符串是一种伪装成引用类型但不是值类型的引用类型。
我可能是完全错误的,我是新来的。
Capitalize()
,该方法会将字符串的内容更改为大写字母。如果再换成您的线路a = "testing";
有a.Capitalize();
,那么你的输出将是“HELLO”,而不是“你好”。不变类型的优点之一是您可以传递引用,而不必担心其他代码会更改值。
MyClass
一个class
类型,即引用类型。在这种情况下,您可以通过myFunction
带ref
/ 的偶数来修改您传递的对象,而不带/out
关键字。myFunction
将会收到指向相同对象的新引用,并且它可以根据需要修改该对象。关键字的不同之处在于,它将收到对相同对象的相同引用。仅当将引用更改为指向另一个对象时,这一点才重要。ref
myFunction
myFunction