【问题标题】:In C#, where do you use "ref" in front of a parameter?在 C# 中,在哪里使用“ref”在参数前面?
【发布时间】:2010-10-22 16:43:27
【问题描述】:

关于“ref”和“out”参数的定义已经存在许多问题,但它们似乎是糟糕的设计。是否存在您认为 ref 是正确解决方案的情况?

似乎您总是可以做一些更清洁的事情。谁能给我一个例子,说明这将是解决问题的“最佳”解决方案吗?

【问题讨论】:

    标签: c# ref


    【解决方案1】:

    在我看来,ref 在很大程度上弥补了声明新实用程序类型的困难以及将信息“附加”到现有信息的困难,这是 C# 自通过 LINQ 诞生以来已采取巨大步骤解决的问题,泛型和匿名类型。

    所以不,我认为它不再有很多明确的用例了。我认为这在很大程度上是该语言最初设计方式的遗留物。

    我确实认为在您需要从函数返回某种错误代码以及返回值的情况下它仍然有意义(如上所述),但没有别的(所以更大的类型不是真的有道理。)如果我在一个项目的所有地方都这样做,我可能会为 thing-plus-error-code 定义一些通用包装器类型,但在任​​何给定的实例中 refout 都可以。

    【讨论】:

    • 如果“返回码+值”结构在项目中被广泛使用,我同意制作包装器类型的想法。
    【解决方案2】:

    好吧,ref 通常用于特殊情况,但我不会将其称为冗余或 C# 的遗留功能。例如,您会看到它(和out)在 XNA 中被大量使用。在 XNA 中,Matrixstruct 并且相当大(我相信 64 字节),通常最好使用 ref 将其传递给函数以避免复制 64 字节,但只是 4 或 8 . 一个专业的 C# 功能?当然。没有太多用处或表明设计不佳?我不同意。

    【讨论】:

      【解决方案3】:

      一个领域是使用小型实用功能,例如:

      void Swap<T>(ref T a, ref T b) { T tmp = a; a = b; b = tmp; }  
      

      我在这里看不到任何“更清洁”的替代品。当然,这不完全是架构级别。

      【讨论】:

        【解决方案4】:

        P/Invoke 是我真正能想到的唯一一个必须使用 ref 或 out 的地方。在其他情况下,它们可能很方便,但就像您说的那样,通常还有另一种更清洁的方法。

        【讨论】:

          【解决方案5】:

          如果你想返回多个对象,由于某些未知的原因没有绑定到一个对象中。

          void GetXYZ( ref object x, ref object y, ref object z);
          

          编辑:divo 建议使用 OUT 参数更适合于此。我不得不承认,他说得有道理。我将把这个答案留在这里,作为记录,这是一个不合适的解决方案。在这种情况下,OUT 胜过 REF。

          【讨论】:

          • 我认为在大多数情况下,您可以重新设计代码,将相似的事物组合成更好的类型,或者以更清晰且希望仍然有效的方式独立返回每个事物。
          • @mquander-完全同意。我想如果你这样做,你要么有一个可能做太多的方法,要么是一个糟糕的对象模型。话虽如此,我确信有人这样做是有充分理由的。
          • 在这种情况下我更喜欢out 参数,因为这样的意图要清楚得多。
          • 嗯,不同之处在于“必须首先初始化传递给 ref 参数的参数。这与 out 不同,其参数在传递之前不必显式初始化”(参见 @987654322 @)
          【解决方案6】:

          我认为最好的用途是您通常看到的那些;您需要同时拥有一个值和一个“成功指标”,这不是函数的例外。

          【讨论】:

          • 但这不是out 参数的完美候选者吗? out 对我来说似乎比 ref 更清晰,并且在 BCL 中也是首选(就像在 &lt;type&gt;.TryParse 方法中一样)。
          • 确实如此。我现在显然需要睡觉了,这里已经很晚了...... ;)
          【解决方案7】:

          ref 有用的一种设计模式是双向访问者。

          假设您有一个Storage 类,可用于加载或保存各种原始类型的值。它处于Load 模式或Save 模式。它有一组称为Transfer 的重载方法,下面是处理int 值的示例。

          public void Transfer(ref int value)
          {
              if (Loading)
                  value = ReadInt();
              else
                  WriteInt(value);
          }
          

          对于其他原始类型会有类似的方法 - boolstring 等。

          然后在一个需要“可转移”的类上,你会写一个这样的方法:

          public void TransferViaStorage(Storage s)
          {
              s.Transfer(ref _firstName);
              s.Transfer(ref _lastName);
              s.Transfer(ref _salary);
          }
          

          同样的单一方法既可以从Storage 加载字段,也可以将字段保存到Storage,这取决于Storage 对象所处的模式。

          实际上,您只是列出了所有需要传输的字段,因此它非常接近声明式编程而不是命令式编程。这意味着您不需要编写两个函数(一个用于读取,一个用于写入),并且鉴于我在这里使用的设计是依赖于顺序的,那么确定这些字段将始终被读取非常方便/以相同的顺序编写。

          一般的一点是,当一个参数被标记为ref时,你不知道该方法是读取它还是写入它,这允许你设计在两个之一中工作的访问者类方向,旨在以对称方式调用(即,visited 方法不需要知道访问者类在哪个方向模式下运行)。

          比较:属性+反射

          为什么要这样做而不是归因于字段并使用反射来自动实现TransferViaStorage 的等效项?因为有时反射慢到足以成为瓶颈(但总是要分析以确保这一点 - 这几乎不是真的,而且属性更接近声明式编程的理想)。

          【讨论】:

            【解决方案8】:

            真正的用途是在你创建一个结构体的时候。 C# 中的结构是值类型,因此在按值传递时总是被完全复制。如果您需要通过引用传递它,例如出于性能原因或因为函数需要对变量进行更改,您将使用 ref 关键字。

            我可以查看是否有人具有包含 100 个值的结构(显然已经存在问题),您可能希望通过引用传递它以防止复制 100 个值。这样做并返回那个大结构并覆盖旧值可能会出现性能问题。

            【讨论】:

            • 在引用类型的情况下它不是很多余,因为当你使用'ref'传递时,获取变量的函数可以将你的引用更改为指向一个完全不同的引用(或 null .) 如果你不使用'ref',它只能改变你事物的现有实例。
            • @mquander 是否真的需要 ref 关键字来做到这一点?我认为你可以在没有'ref'的情况下做到这一点
            • 此信息有误!在 C# 中,引用默认是按值传递的。见yoda.arachsys.com/csharp/parameters.html
            • 正如 divo 的链接中所详述的,'ref' 关键字确实是必要的。如果您不使用 'ref' 关键字,那么您实际上拥有的是对同一对象的两个独立引用。因此,对对象的更改将从函数外部可见,但对引用的更改不可见(即,将其归零。)
            • 关于具有 100 个值的结构的信息不正确。一个结构体(也以 ref 开头)仍然被复制到栈顶,以便被调用的方法完成它的工作。 ref 关键字仅表示在被调用的方法完成其工作后应该再次将结构复制回来。在您的情况下,我会选择引用类型对象(仅传递引用)
            【解决方案9】:

            使用“ref”关键字的明显原因是当您想通过引用传递变量时。例如,将 System.Int32 之类的值类型传递给方法并更改它的实际值。更具体的用途可能是您想要交换两个变量。

            public void Swap(ref int a, ref int b)
            {
               ...
            }
            

            使用“out”关键字的主要原因是从一个方法返回多个值。我个人更喜欢将值包装在专门的结构或类中,因为使用 out 参数会产生相当丑陋的代码。使用“out”传递的参数 - 就像“ref”一样 - 通过引用传递。

            public void DoMagic(out int a, out int b, out int c, out int d)
            {
               ...
            }
            

            【讨论】:

              【解决方案10】:

              当您必须使用“ref”关键字时,有一种明显的情况。如果对象已定义但未在您打算调用的方法范围之外创建,并且您要调用的方法应该执行'new' 来创建它,则必须使用'ref'。例如{object a; Funct(a);} {Funct(object o) {o = new object; o.name = "dummy";} 不会对object 'a' 做任何事情,也不会在编译或运行时抱怨它。它只是不会做任何事情。 {object a; Funct(ref a);} {Funct(object ref o) {o = new object(); o.name = "dummy";} 将导致 'a' 成为一个名为 "dummy" 的新对象。但如果 'new' 已经完成,则不需要 ref(但如果提供,则可以使用)。{object a = new object(); Funct(a);} {Funct(object o) {o.name = "dummy";}

              【讨论】:

                猜你喜欢
                • 2015-03-27
                • 2011-05-10
                • 1970-01-01
                • 1970-01-01
                • 2011-06-26
                • 1970-01-01
                • 1970-01-01
                • 2013-03-21
                相关资源
                最近更新 更多