在 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 关键字,通过引用传递还是按值传递? 例子会有很大帮助。

非常感谢您的帮助。

【问题讨论】:

我不完全理解你的问题......你不能在同一个论点上同时使用 refout。这两个关键字都用于通过引用传递。 我认为您对“按引用传递”和“按值传递引用”有误解 ref 关键字仅对传递值的默认值有用,例如结构、整数、浮点数、布尔值和枚举。 earlz,你可以通过引用传递一个引用,这是有用的。 哇..其实我不知道..哈哈 【参考方案1】:

您将永远不会在 1 个参数上组合 refout。它们都表示“通过引用”。

您当然可以将 ref 参数和 out 参数组合在一种方法中。

refout 的区别主要在于意图。 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 wordStringBuilder 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 关键字与按引用传递和按值传递一起使用的主要内容,如果未能解决你的问题,请参考以下文章

C#中关键字ref和out的区别

C# ref与out关键字区别

C#同样是引用,ref和out到底有什么区别?

c# out ref

C# ref和out总结

C# 初识Ref和Out