“ REF”和“ OUT”关键字有什么区别?
题
我正在创建一个需要传递对象的函数,以便可以通过函数对其进行修改。有什么区别:
public void myFunction(ref MyClass someClass)
和
public void myFunction(out MyClass someClass)
我应该使用哪个?为什么?
解决方案
ref
告诉编译器,该对象在输入函数之前是初始化的,而 out
告诉编译器,该对象将在函数内部初始化。
所以 ref
是双向的, out
仅限。
其他提示
这 ref
修饰符意味着:
- 该值已经设置了,并且
- 该方法可以读取并修改它。
这 out
修饰符意味着:
- 该值未设置,无法通过该方法读取 直到 它设置了。
- 方法 必须 在返回之前将其设置。
假设DOM出现在彼得的隔间,内容涉及有关TPS报告的备忘录。
如果DOM是裁判论点,他将有一份备忘录的印刷副本。
如果Dom是一个辩论,他会让彼得打印一份备忘录的新副本,供他随身携带。
我将尝试通过解释来尝试:
我认为我们了解该价值类型的工作原理吗?价值类型是(int,long,struct等)。当您将它们发送到没有ref命令的函数时,它会复制 数据. 。您对该功能中的数据做的任何事情都只会影响副本,而不是原始数据。 REF命令发送实际数据,任何更改都会影响功能之外的数据。
确定令人困惑的部分,参考类型:
让我们创建一个参考类型:
List<string> someobject = new List<string>()
当你新的时候 某些对象, ,创建了两个部分:
- 保存数据的内存块 某些对象.
- 该数据块的引用(指针)。
现在当您发送 某些对象 在没有ref的方法中,它复制了 参考 指针,而不是数据。因此,您现在拥有这个:
(outside method) reference1 => someobject
(inside method) reference2 => someobject
两个指向同一对象的引用。如果您修改属性 某些对象 使用Reference2它将影响参考1指向的相同数据。
(inside method) reference2.Add("SomeString");
(outside method) reference1[0] == "SomeString" //this is true
如果将参考2删除或将其指向新数据,则不会影响Reference1或Data 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
现在发送时会发生什么 某些对象 通过参考方法?这 实际参考 至 某些对象 被发送到该方法。因此,您现在只有一个对数据的引用:
(outside method) reference1 => someobject;
(inside method) reference1 => 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
您应该使用 out
偏爱您的要求。
出去:
在C#中,方法只能返回一个值。如果您想返回多个值,则可以使用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
因为每个默认值仅 参考 传递到实际对象,因此您始终更改引用背后的对象。
例子:
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
使用前必须初始化变量。out
可以在没有分配的情况下使用变量out
参数必须由使用它的函数视为未分配的值。因此,我们可以使用初始化out
调用代码中的参数,但是当函数执行时,该值将丢失。
对于那些以榜样(像我一样)学习的人,这就是什么 安东尼·科列索夫(Anthony Kolesov)说.
我创建了一些最小的参考文献,外出和其他示例,以说明这一点。我没有涵盖最佳实践,而只是了解差异的例子。
“贝克”
那是因为第一个将您的字符串引用更改为“贝克”。可以更改引用是可能的,因为您通过REF关键字传递了它(=>对字符串引用的引用)。第二个呼叫获得了对字符串的引用的副本。
首先,字符串看起来有些特别。但是字符串只是一个参考类,如果定义
string s = "Able";
然后s是对包含文本“ Able”的字符串类的引用!通过
s = "Baker";
不会更改原始字符串,而只是创建一个新实例,然后让S指向该实例!
您可以使用以下小型代码示例尝试:
string s = "Able";
string s2 = s;
s = "Baker";
Console.WriteLine(s2);
你能指望什么?您将获得的仍然是“能够”的,因为您只是将s中的引用设置为另一个实例,而S2则指向原始实例。
编辑:字符串也是不变的,这意味着根本没有修改现有字符串实例的方法或属性(您可以尝试在文档中找到一个,但不会罚款任何:-))。所有字符串操纵方法返回一个新的字符串实例! (这就是为什么您在使用StringBuilder类时通常会获得更好的性能的原因)
参考 表示已经设置了REF参数中的值,该方法可以读取并修改它。使用REF关键字与说呼叫者负责初始化参数的值相同。
出去 告诉编译器,对象的初始化是函数的责任,该函数必须分配给OUT参数。它不允许无分配。
出去: 返回语句可用于仅从函数返回一个值。但是,使用输出参数,您可以从函数返回两个值。输出参数就像参考参数一样,除了它们从方法中传递数据而不是将数据传输到该方法中。
以下示例说明了这一点:
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();
}
}
}
参考:参考参数是对变量的存储位置的引用。当您通过引用传递参数时,与值参数不同,这些参数不会创建新的存储位置。参考参数代表与提供给该方法的实际参数相同的内存位置。
在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();
}
}
}
参考和淘汰工作,就像通过参考文献传递并通过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);
创作时间:
(1)我们创建调用方法 Main()
(2)它创建列表对象(是参考类型对象)并将其存储在变量中 myList
.
public sealed class Program
{
public static Main()
{
List<int> myList = new List<int>();
在运行时:
(3)运行时在#00上分配存储器,足够宽以存储一个地址(#00 = myList
, ,由于可变名称实际上只是内存位置的别名)
(4)运行时在内存位置在堆上创建列表对象#ff(例如,所有这些地址)
(5)然后运行时将对象的起始地址#ff存储在#00(或用文字存储列表对象的引用在指针中 myList
)
回到创作时间:
(6)然后我们将列表对象作为参数传递 myParamList
到所谓的方法 modifyMyList
并为其分配一个新列表对象
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(或在此方法中指向原始列表对象,并指向新列表对象)
#00中的地址没有更改,并保留对#FF的引用(或原始的 myList
指针没有打扰)。
这 参考 关键字是一项编译器指令,可以跳过(8)和(9)的运行时代码的生成,这意味着方法参数不会堆分配。它将使用原始的#00指针在#FF上的对象上操作。如果原始指针未初始化,运行时将停止抱怨,因为该变量未初始化
这 出去 关键字是一个编译器指令,与Ref几乎相同,在(9)和(10)上进行了轻微的修改。编译器期望该参数是非初始化的,并将继续(8),(4)和(5)在堆上创建一个对象,并将其起始地址存储在参数变量中。不会丢弃非传统错误,并且任何先前存储的参考文献都将丢失。
它们几乎相同 - 唯一的区别是,您传递的变量不需要初始化,并且使用REF参数的方法必须将其设置为某物。
int x; Foo(out x); // OK
int y; Foo(ref y); // Error
REF参数用于可能会修改的数据,OUT参数用于数据,该数据是该函数(例如Int.TryParse)的附加输出,这些输出已经使用了某物的返回值。
下面我显示了一个示例,同时使用 参考 和 出去. 。现在,大家都将被裁定和退出。
在下面提到的示例中,我发表评论 // myrefobj = new myClass {name =“ ref offe offeard nater !!”};行,会出现错误的说法 “使用未分配的本地变量'myrefobj'”, ,但是没有这样的错误 出去.
在哪里使用ref: :当我们调用具有IN参数的过程时,将使用相同的参数来存储该proc的输出。
在哪里使用: 当我们调用没有参数的过程时,将使用相同的参数来返回该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”时,它在两种情况下都可以使用Wheather U初始化该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
参考和方法超载
参考文献和外部都不能同时超载。但是,在运行时,参考和外出的处理方式有所不同,但在编译时间对其进行了相同的处理(CLR在两者创建IL用于Ref和Out时没有区分)。
从接收参数的方法的角度来看, 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
未修改。
请注意,与c#中的vb.net编写的构造函数没有任何假设 out
参数,无条件清除所有字段。上面提到的奇数行为不会完全用VB或C#中的代码编写,但是当C#呼叫代码写入VB.NET中编写的方法时,可能会发生。
如果要将参数传递为ref,则应在将参数传递给函数之前将其初始化,否则编译器本身将显示错误。但是,如果参数为OUT,则不需要在将其传递给对象参数之前初始化对象参数方法。您可以在调用方法本身中初始化对象。
除了允许您重新分配他人的变量到类的其他实例,返回多个值等, 使用 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
对“将占位符变量传递给我的方法。在打电话给我的方法之前 将要 到我完成的时候有所不同
顺便说一句,在C#7.2中有一个 in
修饰符也是如此
这会防止该方法将传递的实例交换为不同的实例。想想这是对数百万开发人员说的“传递您的原始变量参考,我保证不会将精心制作的数据换成其他内容”。 in
有一些特殊性,在某些情况下,例如可能需要隐性转换以使您的短期兼容 in int
编译器将暂时制作INT,使您的简短扩大,并通过参考并完成。它可以做到这一点,因为您已声明自己不会搞砸它。
微软与 .TryParse
数字类型的方法:
int i = 98234957;
bool success = int.TryParse("123", out i);
通过将参数标记为 out
他们在这里积极宣布:“我们是 确实 要改变您的98234957的精心制作价值,以获取其他东西”
当然,他们必须这样做,因为解析价值类型之类您正在创建的库:
public void PoorlyNamedMethod(out SomeClass x)
你可以看到这是一个 out
, ,因此,您可以知道,如果您花费数小时来处理数字,创建完美的躯体:
SomeClass x = SpendHoursMakingMeAPerfectSomeClass();
//now give it to the library
PoorlyNamedMethod(out x);
好吧,这是浪费时间,花了所有这些时间来完成那个完美的课程。它肯定会被扔掉,并由不明的方法取代
请注意,该功能内部传递的参考参数直接处理。
例如,
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;
}
这会写狗,而不是猫。因此,您应该直接处理某些对象。
我可能不太擅长这一点,但是肯定会按值而不是参考来传递(即使在技术上是参考类型并且在堆上生活)?
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"!!!!
这就是为什么您需要参考,如果您希望更改在功能范围之外存在之外,则您不会传递参考。
据我所知,您只需要对结构/值类型和字符串本身需要参考,因为字符串是假装它的参考类型,但不是值类型。
不过,我可能在这里完全错了,我是新手。