【问题标题】:When to use ref and when it is not necessary in C#在 C# 中何时使用 ref 以及何时不需要
【发布时间】:2016-06-06 09:43:46
【问题描述】:

我有一个对象,它是程序的内存状态,还有一些其他工作函数,我将对象传递给它以修改状态。我一直通过 ref 将它传递给工作函数。但是我遇到了以下功能。

byte[] received_s = new byte[2048];
IPEndPoint tmpIpEndPoint = new IPEndPoint(IPAddress.Any, UdpPort_msg);
EndPoint remoteEP = (tmpIpEndPoint);

int sz = soUdp_msg.ReceiveFrom(received_s, ref remoteEP); 

这让我很困惑,因为 received_sremoteEP 都从函数返回内容。为什么remoteEP 需要refreceived_s 不需要?

我也是一名 c 程序员,所以我无法从脑海中获取指针。

编辑: 看起来 C# 中的对象是指向底层对象的指针。因此,当您将对象传递给函数时,您可以通过指针修改对象内容,并且传递给函数的唯一内容是指向对象的指针,因此不会复制对象本身。如果您希望能够在函数中切换或创建一个新对象,就像双指针一样,您可以使用 ref 或 out 。

【问题讨论】:

    标签: c# ref


    【解决方案1】:

    简答:阅读我的article on argument passing

    长答案:当引用类型参数按值传递时,仅传递引用,不是对象的副本。这就像在 C 或 C++ 中传递一个指针(按值)。调用者不会看到参数本身值的变化,但会看到引用指向的对象的变化

    当一个参数(任何类型)通过引用传递时,这意味着调用者可以看到参数的任何更改 - 参数的更改更改到变量。

    当然,这篇文章更详细地解释了所有这些:)

    有用的答案:你几乎不需要使用 ref/out。它基本上是一种获得另一个返回值的方法,通常应该避免,因为这意味着该方法可能试图做太多事情。情况并非总是如此(TryParse 等是合理使用 out 的典型示例),但使用 ref/out 应该是相对罕见的。

    【讨论】:

    • 我认为你的短答案和长答案混淆了;这是一篇大文章!
    • @Outlaw:是的,但简短的回答本身,即阅读文章的指令,只有 6 个字长:)
    • +1 所有好东西。我喜欢使用ref显式地表示我希望这个对象被用作参考对象,即我可以改变其中的一些东西。只是发现它使代码更具可读性。
    • @Liam 像你一样使用 ref 可能会让你觉得更明确,但实际上可能会让其他程序员(那些知道该关键字做什么的人)感到困惑,因为你实际上是在告诉潜在的调用者, "我可以修改您在调用方法中使用的变量,即将它重新分配给不同的对象(如果可能的话,甚至可以分配给null)所以不要挂到它,或者确保在我完成它时验证它”。这非常强大,并且与“可以修改此对象”完全不同,只要您将对象引用作为参数传递,情况总是
    • @M.Mimpen:C# 7 将(希望)允许if (int.TryParse(text, out int value)) { ... use value here ... }
    【解决方案2】:

    将非 ref 参数视为指针,将 ref 参数视为双指针。这对我帮助最大。

    您几乎不应该通过 ref 传递值。我怀疑如果不是出于互操作问题,.Net 团队永远不会将它包含在原始规范中。处理ref参数解决的大多数问题的OO方式是:

    对于多个返回值

    • 创建表示多个返回值的结构

    对于因方法调用而在方法中发生变化的基元(方法对基元参数有副作用)

    • 将对象中的方法实现为实例方法,并将对象的状态(而不是参数)作为方法调用的一部分进行操作
    • 使用多返回值解决方案并将返回值合并到您的状态
    • 创建一个对象,其中包含可由方法操作的状态并将该对象作为参数传递,而不是原语本身。

    【讨论】:

    • 天哪。我必须阅读这 20 倍才能理解它。对我来说听起来像是一堆额外的工作,只是为了做一些简单的事情。
    • .NET 框架不遵循您的#1 规则。 ('为多个返回值创建结构')。以IPAddress.TryParse(string, out IPAddress)为例。
    • @SwenKooij 你是对的。我假设在他们使用参数的大多数地方(a)他们正在包装一个 Win32 API,或者(b)它是早期的,C++ 程序员正在做出决定。
    • @SwenKooij 我知道这个回复晚了很多年,但确实如此。我们习惯了 TryParse,但这并不意味着它很好。如果我们有var candidate = int.TrialParse("123"); if (candidate.Parsed) { /* do something with candidate.Value */ } 而不是if (int.TryParse("123", out var theInt) { /* use theInt */ } 会更好,它有更多代码,但更符合C# 语言设计。
    【解决方案3】:

    您可能编写一个完整的 C# 应用程序并且永远不会通过 ref 传递任何对象/结构。

    我有一位教授告诉我:

    你唯一会使用 refs 的地方是你:

    1. 想要传递一个大对象(即对象/结构有 它里面的对象/结构到多个级别)并复制它会 又贵又
    2. 您正在调用框架、Windows API 或其他需要 它。

    不要仅仅因为你可以就这样做。你可能会被一些人咬伤 如果您开始更改参数中的值并且没有更改,则会出现令人讨厌的错误 注意。

    我同意他的建议,在我上学五年多的时间里,除了调用框架或 Windows API 之外,我从来没有需要它。

    【讨论】:

    • 如果你打算实现“Swap”,通过 ref 传递可能会有所帮助。
    • @Chris 如果我对小对象使用 ref 关键字会不会有什么问题?
    • @TechnikEmpire “但是,在被调用函数范围内对对象的更改不会反映给调用者。”那是错的。如果我将 Person 传递给 SetName(person, "John Doe"),则 name 属性将更改,并且该更改将反映给调用者。
    • @M.Mimpen 评论已删除。那时我几乎没有学过 C#,而且很明显是在说我的 *$$。感谢您提请我注意。
    • @Chris - 我很确定传递一个“大”对象并不昂贵。如果你只是按值传递它,你仍然只是传递一个指针,对吗?
    【解决方案4】:

    由于 received_s 是一个数组,因此您正在传递一个指向该数组的指针。该函数在适当的位置操作现有数据,而不更改底层位置或指针。 ref 关键字表示您将实际指针传递给该位置并在外部函数中更新该指针,因此外部函数中的值将发生变化。

    例如字节数组是指向前后同一个内存的指针,内存刚刚更新。

    Endpoint 引用实际上是将外部函数中指向 Endpoint 的指针更新为函数内部生成的新实例。

    【讨论】:

      【解决方案5】:

      将 ref 视为通过引用传递指针的意思。不使用 ref 意味着您正在按值传递指针。

      更好的是,忽略我刚才所说的(这可能会产生误导,尤其是值类型)并阅读This MSDN page

      【讨论】:

      • 其实不然。至少第二部分。无论您是否使用 ref,任何引用类型都将始终通过引用传递。
      • 实际上,经过进一步思考,结果并不正确。引用实际上是按值传递的,没有 ref 类型。也就是说,更改引用指向的值会更改原始数据,但更改引用本身不会更改原始引用。
      • 非引用引用类型不通过引用传递。对引用类型的引用按值传递。但是,如果您想将引用视为指向正在被引用的事物的指针,那么我所说的是有道理的(但这样想可能会产生误导)。因此我的警告。
      • 链接失效了——试试这个——docs.microsoft.com/en-us/dotnet/csharp/language-reference/…
      【解决方案6】:

      虽然我总体上同意 Jon Skeet 的回答和其他一些回答,但有一个使用 ref 的用例,那就是加强性能优化。在性能分析过程中观察到,设置方法的返回值会对性能产生轻微影响,而使用ref 作为参数,将返回值填充到该参数中会消除这个轻微的瓶颈。

      这只有在优化工作达到极端水平、牺牲可读性、可测试性和可维护性以节省毫秒或分毫秒的情况下才真正有用。

      【讨论】:

        【解决方案7】:

        我的理解是,从 Object 类派生的所有对象都作为指针传递,而普通类型(int,struct)不作为指针传递,需要 ref。我不确定字符串(它最终是从 Object 类派生的吗?)

        【讨论】:

        • 这可能需要一些澄清。价值类型和参考类型之间有什么区别。 Ans 为什么这与在参数上使用 ref 关键字有关?
        • 在 .net 中,除了指针、类型参数和接口之外的所有内容都派生自 Object。重要的是要理解,“普通”类型(正确称为“值类型”)也继承自对象。你是对的:值类型(默认)是按值传递的,而引用类型是通过引用传递的。如果你想修改一个值类型,而不是返回一个新的值类型(像方法内的引用类型一样处理它),你必须在它上面使用 ref 关键字。但这是不好的风格,除非你绝对确定你必须这样做,否则你不应该这样做:)
        • 回答你的问题:字符串派生自对象。它是一种引用类型,行为类似于值类型(出于性能和逻辑原因)
        【解决方案8】:

        首先遵循零规则,在所涉及的类型的上下文中,基元通过值(堆栈)传递,非基元通过引用(堆)传递。

        涉及的参数默认通过Value传递。 很好的帖子,详细解释了事情。 http://yoda.arachsys.com/csharp/parameters.html

        Student myStudent = new Student {Name="A",RollNo=1};
        
        ChangeName(myStudent);
        
        static void ChangeName(Student s1)
        {
          s1.Name = "Z"; // myStudent.Name will also change from A to Z
                        // {AS s1 and myStudent both refers to same Heap(Memory)
                        //Student being the non-Primitive type
        }
        
        ChangeNameVersion2(ref myStudent);
        static void ChangeNameVersion2(ref Student s1)
        {
          s1.Name = "Z"; // Not any difference {same as **ChangeName**}
        }
        
        static void ChangeNameVersion3(ref Student s1)
        {
            s1 = new Student{Name="Champ"};
        
            // reference(myStudent) will also point toward this new Object having new memory
            // previous mystudent memory will be released as it is not pointed by any object
        }
        

        我们可以说(带有警告)非原始类型只不过是指针 当我们通过 ref 传递它们时,我们可以说我们正在传递 双指针

        【讨论】:

        • 所有 参数在 C# 中默认按值传递。 任何参数都可以通过引用传递。对于引用类型,传递的值(通过引用或值)本身就是一个引用。这完全独立于它的传递方式。
        • 同意@Servy! “当我们听到使用“引用”或“值”这些词时,我们应该非常清楚我们的意思是参数是引用还是值参数,或者我们的意思是所涉及的类型是引用还是值类型' Little我这边很困惑,当我说首先使用零规则,基元通过值(堆栈)传递,非基元通过引用(堆)传递我的意思是涉及的类型,而不是参数!当我们讲参数你是对的,所有的参数都是传值的,默认在C#中。
        • 说一个参数是一个引用或值参数不是一个标准术语,你的意思真的很模棱两可。参数的类型可以是引用类型,也可以是值类型。任何参数都可以通过值或引用传递。这些是任何给定参数的正交概念。您的帖子对此的描述不正确。
        • 您通过引用或值传递参数。术语“按引用”和“按值”不用于描述类型是值类型还是引用类型。
        • 不,这不是感知问题。当您使用不正确的术语 来指代某事物时,该语句就变成了错误。正确的陈述使用正确的术语来指代概念很重要。
        猜你喜欢
        • 1970-01-01
        • 2014-10-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-07-01
        相关资源
        最近更新 更多