【问题标题】:Best Practice: ByRef or ByVal? in .Net最佳实践:ByRef 还是 ByVal?在.Net
【发布时间】:2008-11-14 14:12:20
【问题描述】:

在 ByRef 和 ByVal 之间进行选择时需要考虑哪些事项。

我了解两者之间的区别,但我不完全了解 ByRef 是否可以节省资源,或者我们是否需要在 .Net 环境中担心这一点。

如果功能在某种情况下无关紧要,您如何在两者之间做出决定?

【问题讨论】:

  • 我不确定我是否正确地提出了这个问题。是否有一个 .Net 相当于将 ByRef 作为 ReadOnly 传递(就像在 Java 中一样)?节省资源?
  • 请展示Java语法,我们会说你是否可以在C#中做同样的事情。请记住,Java 纯粹是按值传递...
  • 在其他语言中,为了节省资源,可以通过只读引用传递大对象以节省资源。它的作用类似于按值传递的引用,但不会复制。
  • 是的,但是在 Java 中没有类似的东西......这就是为什么我要一个 Java 示例。

标签: .net byref byval


【解决方案1】:

关于这个有很多错误信息。主要是你了解difference between value types and reference typesdifference between pass by value and pass by reference

您几乎总是希望按值传递。通过引用传递几乎总是用于“我想返回多个结果,而不仅仅是通过将内容添加到传入的列表中”。使用 pass-by-reference 方法的经典示例是 Int32.TryParse,其中返回值是成功/失败,解析值由 out 参数“返回”。

【讨论】:

  • +1。在我看来,通过 ref 并在返回 void 的方法内部进行更改是一个常见的“错误”——它会误导该方法的作用。
  • 许多开发人员仍然倾向于使用这种模式 - 可能是因为他们的背景来自 C++ 或 COM,其中 ret vals 总是错误代码并且这种模式是必要的?
【解决方案2】:

所有类型的默认值为 byValue,但重要的是要了解这两个选项对于“引用类型”(类)而不是值类型的含义。 (结构)。

对于引用类型,如果在方法中声明了引用类型变量,则该变量是方法堆栈帧中的内存位置。它不在堆上。当您初始化该变量(使用 new 或工厂等)时,您已经在堆上创建了一个实际对象,并且该对象的地址存储在方法堆栈框架中声明的引用变量中。

当您将引用类型传递给另一个方法 byVal 时,您正在创建存储在调用方法堆栈中的地址的副本,并将该值的副本(指针地址)传递给被调用的方法,它存储在调用方法堆栈中的新内存槽。在被调用的方法内部,新克隆的变量直接指向堆上的同一个对象。所以使用它可以改变同一个对象的属性。但是您不能更改原始引用变量(在调用方法堆栈上)指向的堆对象。 如果,在被调用的方法中我写

  myVar = new object();

调用方法中的原始变量不会更改为指向新对象。

如果我通过引用传递一个引用类型,otoh,我正在传递一个指向调用方法堆栈中声明变量的指针(其中包含指向堆上对象的指针)因此它是指向对象指针的指针.它指向调用方法栈上的内存位置,它指向堆上的对象。
所以现在,如果我在被调用方法中更改变量的值,通过将其设置为一个新的 object(),如上所述,因为它是对调用方法中变量的“引用”,我实际上是在更改哪个对象调用方法中的变量指向。所以被调用方法返回后,调用方法中的变量将不再指向堆上的同一个原始对象。

【讨论】:

  • 这当然是我在 Java 世界中学到的,但我不确定在 .Net 中是否如此?
  • 我不是 Java 人,但我听说 Java 甚至没有值类型。每个变量都是引用类型...错了吗?
  • 是的,这是错误的。 Java 有值类型。它没有的是用户定义的值类型。
  • 啊,所以,你的意思是像 int、short、ulong、decimal、char 等......在每个方法堆栈框架上实现为值类型,但实现了任何自定义用户定义类型作为堆上的引用类型?
  • 仅供参考,在 Java 中,是的,有 int、short、double 等值类型……还有像 Integer、Double 等类类型……在 .NET int 中是只是 Int32 的别名,它是带有方法的值类型,等等……在 Java 中,它们根本不一样; Integer 是一个包装了 int 的类,它为它提供了属性(甚至将 int 传递给构造函数)。在 .NET 中,您甚至可以说 42.ToString() -- 尝试在 Java 中这样做。
【解决方案3】:

ByVal 应该是您的“默认值”。除非您有特定理由使用 ByRef,否则请使用它

【讨论】:

  • According to Microsoft: Visual Basic 中的默认设置是按值传递参数。 因此,推荐的做法通常是省略ByVal 关键字。请参阅this answer 了解更多信息。
  • 这与那篇文章有一个令人困惑的矛盾,即默认值为 ByRef 但参数将传递参考或值,具体取决于其他因素。 link
  • 我认为这是一种奇怪的方式,试图避免详细介绍 value and reference types 之间的区别。
【解决方案4】:

在.net中传递对象ByVal不会复制对象并且不会消耗比ByRef更多的资源,指针仍然传递给函数。运行时只是确保您不能修改函数中的指针并为其返回不同的值。您仍然可以更改对象内的值,并且您将在函数之外看到这些更改。这就是为什么 ByRef 很少使用的原因。仅当您想要一个函数来更改返回的实际对象时才需要它;因此是一个输出参数。

【讨论】:

  • 这是真的吗?为什么它被否决了?也许我没有正确地问这个问题,因为这更符合我正在寻找的答案。
  • 这在技术上是正确的,但非常具有误导性。当通过Val传递一个对象时,“对象”没有被复制,而是对象的地址被复制,并传递给被调用的方法。传递byRef时,不复制对象的地址,传递的是addesss的地址。
  • 从技术上讲,这不是真的,即使对于课程也是如此。但对于值类型来说,情况就更不真实了。 -- 对于类,您在传递 byval 时将指针的副本传递给类。同样,该指针被复制。 (因此,如果您在被调用者中重新分配指针,则调用者将不会拾取更改。)对于值类型,如果您按值传递,由于没有指针,因此会制作一份 COMPLETE 副本;例如,如果您传递一个 200 字节的结构 byval,则每次调用都会复制 200 个字节 - 而 byref 仅复制 4(或 8 个,取决于架构)字节指针。
【解决方案5】:

仅当参数为“输出”参数时才使用“ByRef”。否则使用“ByVal”。在明确不应该返回值的参数上使用“ByRef”是危险的,并且很容易产生错误。

【讨论】:

  • 没有。如果参数是“输出”参数,请使用“Out”。如果参数是“参考”参数,请使用“ByRef”。
【解决方案6】:

我认为永远不应该使用 ByRef——这是一种不好的做法。我什至会将其应用于允许函数返回多个值(通过 ByRef 参数)的典型用例。函数返回包含这些多个返回值的结构化响应会更好。如果一个函数只通过它的 return 语句返回值,那就更清楚更明显了。

【讨论】:

  • 是的,完全正确。它包含在 .NET 中是一个主要的脑残。
  • 我不同意。它对于诸如经典交换方法之类的东西非常有用:“Swap(ref T a, ref T b) where T: struct { ... }”(如果我的语法错误,请道歉)
  • 如果没有ByRef,如何使用Threading.Interlocked.Increment 和其他此类成员?
  • 当你需要它时,它正是你所需要的。大多数时候,但你没有。所以应该只在你特别需要的时候使用。
  • 完全同意,带有 ByRef 的函数是一种非常糟糕的做法。尤其是对于那些来自函数式编程背景的人来说,这很奇怪。
【解决方案7】:

将某些参数标记为 ByRef 向函数的用户显示分配给该参数的 变量 **将被修改。****

如果您对所有参数都使用 ByRef,则无法分辨哪些变量被函数修改,哪些变量只是被它读取。 (除了偷看函数源!)

【讨论】:

    【解决方案8】:

    根据 Microsoft 的说法,选择 ByVal 或 ByRef 会影响足够大的值的性能(请参阅Passing Arguments by Value and by Reference (Visual Basic)):

    性能。虽然传递机制会影响性能 在您的代码中,差异通常是微不足道的。一个例外 这是一个通过 ByVal 的值类型。在这种情况下,Visual Basic 复制参数的整个数据内容。因此,对于一个 结构体等大值类型,可以更高效通过 它通过参考。

    [强调添加]。

    【讨论】:

      【解决方案9】:
      Sub last_column_process()
      Dim last_column As Integer
      
      last_column = 234
      MsgBox last_column
      
      trying_byref x:=last_column
      MsgBox last_column
      
      trying_byval v:=last_column
      MsgBox last_column
      
      End Sub
      
      Sub trying_byref(ByRef x)
      x = 345
      End Sub
      
      Sub trying_byval(ByRef v)
      v = 555
      End Sub
      

      【讨论】:

        【解决方案10】:

        我会尽量简化很多困惑。你基本上有4个选择:

        1. 传递值类型 byVal
        2. 通过引用传递值类型
        3. 通过Val传递一个对象
        4. 通过引用传递对象

        有人说你应该永远使用 byRef。虽然它们在技术上是正确的,但有一件事是肯定的。您应该从不使用从不这个词。如果您是从头开始设计系统,那么应该不惜一切代价避免使用 byRef。使用它会暴露一个设计缺陷。然而,在现有系统上工作可能无法提供足够的灵活性来实现良好的设计。有时你必须进行consessions,即使用byRef。例如,如果您可以使用 byRef 在 2 天内完成修复,那么这可能比重新发明轮子并花费一周时间来获得相同的修复只是为了避免使用 byRef。

        总结:

        1. 在值类型上使用 byVal:将值传递给函数。这是首选的方式 设计功能。
        2. 在值类型上使用 byRef:对于从函数返回多个值很有用。如果你 正在创建一个需要返回多个值的函数 一个现有的系统,这可能比创建一个对象(并设置属性和处置)更好 一个功能。
        3. 在对象上使用 byVal:将对象的指针传递给函数。该功能可以 修改对象。
        4. 在对象上使用 byRef:将指向对象指针的指针传递给函数。允许 更改调用者指向的对象。这可能会导致一些 很难找到错误,我想不出任何好的理由来使用它。 不代表没有,但如果有的话就很少 相差甚远。

        【讨论】:

          猜你喜欢
          • 2010-09-29
          • 1970-01-01
          • 2017-12-05
          • 1970-01-01
          • 2015-02-15
          • 1970-01-01
          • 2010-10-06
          • 1970-01-01
          • 2011-03-21
          相关资源
          最近更新 更多