在 C# 中将 REF 和 OUT 关键字与按引用传递和按值传递一起使用
Posted
技术标签:
【中文标题】在 C# 中将 REF 和 OUT 关键字与按引用传递和按值传递一起使用【英文标题】:Using REF & OUT keywords with Passing by Reference & Passing by Value in C# 【发布时间】:2010-12-13 14:15:17 【问题描述】:这是我目前所理解的:
按值传递
按值传递意味着传递参数的副本。 对该副本的更改不会更改原件。
通过引用传递
通过引用传递意味着对原始的引用被传递。 对参考的更改会影响原件。
REF 关键字
REF 告诉编译器对象在进入函数之前已经初始化。 REF 表示该值已设置,因此该方法可以读取并修改它。 REF 有两种方式,既进又出。
OUT 关键字
OUT 告诉编译器对象将在函数内部初始化。 OUT 表示该值尚未设置,因此必须在调用 return 之前设置。 OUT只是一种方式,就是out。
问题
那么在什么情况下你会结合使用 ref 和 out 关键字,通过引用传递还是按值传递? 例子会有很大帮助。
非常感谢您的帮助。
【问题讨论】:
我不完全理解你的问题......你不能在同一个论点上同时使用ref
和 out
。这两个关键字都用于通过引用传递。
我认为您对“按引用传递”和“按值传递引用”有误解
ref
关键字仅对传递值的默认值有用,例如结构、整数、浮点数、布尔值和枚举。
earlz,你可以通过引用传递一个引用,这是有用的。
哇..其实我不知道..哈哈
【参考方案1】:
您将永远不会在 1 个参数上组合 ref
和 out
。它们都表示“通过引用”。
您当然可以将 ref 参数和 out 参数组合在一种方法中。
ref
和out
的区别主要在于意图。 ref 表示 2 路数据传输,out 表示 1 路。
但除了意图之外,C# 编译器会跟踪明确赋值,这会产生最显着的差异。它还可以防止误用(读取)输出参数。
void SetOne(out int x)
int y = x + 1; // error, 'x' not definitely assigned.
x = 1; // mandatory to assign something
void AddTwo(ref int x)
x = x + 2; // OK, x is known to be assigned
void Main()
int foo, bar;
SetOne(out foo); // OK, foo does not have to be assigned
AddTwo(ref foo); // OK, foo assigned by SetOne
AddTwo(ref bar); // error, bar is unassigned
【讨论】:
Ray,在我的示例中,AddTwo(out foo)
将修改 foo
。这就是 by-ref 部分。它的工作方式取决于实现,但对于 ref 和 out 通常是相同的。并且可能是堆栈偏移量而不是指针。
对不起。是的,我的测试代码中有一个错误。在更正它之后,我看到“out”参数不是由 OUT 传递的,而是在 fae 中通过引用传递,并带有明确分配的附加约束。请接受我的道歉。【参考方案2】:
你通过 ref 传递一些你想被另一个函数读写的东西,所以你应该传递初始化的变量以便正确读取。
编辑:示例:
void mymethod(ref int a)
a++;
您将希望由另一个函数写入的内容作为 OUT 传递,但您不需要初始化它,因为该函数不会读取它,而只会写入。
编辑:示例:
void mymethod2(out string a)
a="hello";
【讨论】:
【参考方案3】:编辑:我已经更正了这个答案,以反映 C# 中的“out”关键字没有达到您的预期(例如,计算机科学意义上的真正 OUT 参数)。我最初声明 'out' 是按值传递 OUT 但被证明是错误的。
您不能同时使用“out”和“ref”。 C#(NET Framework)中有三种调用约定:
无关键字 = 按值传递 (IN) 'out' 关键字 = 通过引用传递 (REF),调用前没有明确的分配要求 'ref' 关键字 = 通过引用传递 (REF),调用前有明确的分配要求C# 没有真正的 OUT 或 IN-OUT 参数能力。
要查看 C# 中的 'out' 参数不是真正的 OUT 参数,可以使用以下代码:
public class Test
Action _showValue;
public void Run()
string local = "Initial";
_showValue = () => Console.WriteLine(local.ToString()); ;
Console.WriteLine("Passing by value");
inMethod(local);
Console.WriteLine("Passing by reference with 'out' keyword");
outMethod(out local);
Console.WriteLine("Passing by reference with 'ref' keyword");
refMethod(ref local);
void inMethod(string arg)
_showValue();
arg = "IN";
_showValue();
void outMethod(out string arg)
_showValue();
arg = "OUT";
_showValue();
void refMethod(ref string arg)
_showValue();
arg = "REF";
_showValue();
输出是:
Passing by value
Initial
Initial
Passing by reference with 'out' keyword
Initial
OUT
Passing by reference with 'ref' keyword
OUT
REF
如您所见,'out' 和 'ref' 实际上都经过 REF。唯一的区别在于编译器如何处理它们以进行明确的赋值。
【讨论】:
out
不是按值传递。
这取决于您正在阅读的计算机科学教科书,但大多数人认为 IN、OUT 和 IN-OUT 都是“按值传递”的所有示例。按值传递的基本概念是,当它被传递时,您复制值而不是指向它的指针。顺便说一句,共有五种公认的参数传递方案:IN、OUT、IN-OUT、REF 和 NAME。 C# 只有 IN、OUT 和 REF。路过NAME很有趣。如果您好奇,请查看 Algol-68。
我忘了很多回溯语言也可以通过引用传递出去。这在它们多次返回时会有所不同,例如使用 Scheme 调用/cc 或任何包含真正生成器的语言。
我很抱歉。我对“out”关键字的 C# 用法不正确。正如 Mehrdad、Henk 和其他人所说,它实际上并没有创建一个 OUT 参数,而是一个 REF 参数,对明确的分配有额外的约束。对困惑感到抱歉。不过,我的第一条评论是准确的。我将编辑我的答案以避免将来出现混淆。
不用担心;很容易混淆这些东西。需要明确的是, out 和 ref 都施加了明确的分配要求。 (1) 在调用方 (1a) ref 变量必须在调用之前明确分配 (1b) out 变量不需要在调用之前明确分配,(1c) out 变量在调用之后是 DA。 (2) 在被调用方,(2a) ref 变量自始至终都是 DA,(2b) out 变量在进入时不是 DA,(2c) out 变量在每次正常返回之前必须是 DA。【参考方案4】:
帮助不胜感激
正确谨慎地使用语言会提高您的理解力。
按值传递意味着传递参数的副本。
是的,这完全正确。
对该副本的更改不会更改原件。
不完全是。首先要仔细区分值和变量。考虑:
class Foo public int x;
...
void N()
Foo blah = new Foo();
blah.x = 0;
M(blah);
...
void M(Foo foo)
foo.x = 123; // changes blah.x
foo = null; // does not change blah
这里的变量是 x、blah 和 foo。 x 是一个字段,blah 是一个本地,foo 是一个形式参数。
这里的值是 null、0、123,以及对 Foo 实例的引用。 该参考是一种价值。了解这一事实至关重要。
通过将变量 blah 的值复制到变量 foo 中来传递 blah 值的副本。 blah 的值是对 Foo 实例的引用。
M 可以改变变量 x 的值,因为 M 具有 blah 值的副本,它是对 Foo 的引用。当 M 将 foo 的内容更改为 null 时,不会改变 blah; foo 包含 blah 值的副本。
通过引用传递意味着对原始的引用被传递。
谨慎选择措辞。什么是“原版”?
这会更好地表述为“通过引用传递意味着传递对必须是变量的参数的引用”。一种更简单的思考方式是“通过引用传递使参数成为作为参数传递的变量的别名”。
对参考的更改会影响原件。
由于形参和实参互为别名,所以对引用的更改并不影响原始;参考是原件。它们都是同一个变量。
REF 告诉编译器对象在进入函数之前已经初始化。
“对象”是没有意义的。你的意思是“变量”。
"ref" 不会“告诉编译器变量已初始化”。相反,“ref”告诉编译器“您,编译器,必须验证变量是否已初始化”。那是相当不同的!
REF表示该值已经设置,
不,ref 要求 变量 已经设置。没有所谓的“设定价值”。
因此该方法可以读取并修改它。
“它”是指“变量”。
REF 有两种方式,既进又出。
正确。
OUT 告诉编译器对象将在函数内部初始化。
停止使用“对象”来表示“变量”。如果你停止混淆完全不同的事物,你会更清楚地理解事物。 变量不是对象。变量是存储位置,其中一些可能包含值,其中一些值可能是对对象的引用.
所以,out 告诉编译器该变量将在方法内部进行初始化,是的,但这并不完全正确。您忘记了该方法将引发异常或该方法将进入无限循环的情况——这些也是合法的情况。
OUT 表示该值尚未设置,
同样,“值”是指“变量”。但这并不准确。将初始化变量作为“out”参数传递是完全合法的。毫无意义,但合法。
因此必须在调用 return 之前设置。
“return”没有被调用;方法被调用。但是是的,方法必须在正常返回之前给变量赋值。
OUT只是一种方式,就是out。
没错。
那么在什么场景下你会结合使用 ref 和 out 关键字
没有这样的场景。
【讨论】:
【参考方案5】:您了解通过任何一种方式的动态。一些参数场景可能是:
ref int num
用于输入/输出参数。函数可能修改其中的值。
out int num
like ref 除了函数必须在返回之前为其赋值。
通常输出参数适用于函数必须返回多个值的情况,因为函数只有一个返回值(尽管它可以是复合的)。
有时数据访问提供程序(如某些 ADO.NET 方法)使用输出参数来传递信息。一些数据访问方法模仿具有输入/输出参数的数据库存储过程。
编辑: 引用类型的一个规定是参数 ref StringBuilder word
或 StringBuilder word
(按值)行为相同 - 外部字符串受到影响,尽管底层实现可能略有不同,因为在该点的焦点是引用而不是堆上的值。
【讨论】:
所有这些中最简单、最直接的答案。【参考方案6】:如果你懂 c++,也许这会对你有所帮助:
void Foo(Bar) // pass by value c# (if it's a value type ;))
void Foo(Bar) // c++
void Foo(Bar) // pass by reference c# (if it's a reference type ;))
void Foo(Bar&) // c++
void Foo(ref Bar) // c#
void Foo(Bar*) // c++
void Foo(out Bar) // c#
void Foo(Bar**) // c++ (the contents of *Bar needs to be filled up)
【讨论】:
【参考方案7】:如果您有一个方法需要返回多个值,则使用OUT
关键字很有用。例如,查看int.TryParse()
之类的方法。
使用REF
更多是为了明确对象。请记住,传递给方法的任何非原始方法本质上都是通过引用传递的,在普通托管代码中并没有太多需要它。通过声明 REF 关键字,您声明参数可能会在方法的主体中被修改,因此调用代码应该知道它(因此您必须在调用代码中显式添加 ref
.
【讨论】:
以上是关于在 C# 中将 REF 和 OUT 关键字与按引用传递和按值传递一起使用的主要内容,如果未能解决你的问题,请参考以下文章