我有一个对象,它是我程序的内存状态,也有一些其他的辅助函数,我通过它来修改状态。我一直把它通过引用传递给工作者函数。不过,我遇到了以下功能。
1 2 3 4 5
| 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_s和remoteEP都从函数返回了内容。为什么remoteEP需要ref而received_s不需要?
我也是一个C程序员,所以我在把指针从我的头脑中拿出来时遇到了问题。
编辑:看起来C中的对象是指向引擎盖下对象的指针。因此,当您将一个对象传递给一个函数时,您可以通过指针修改对象内容,传递给该函数的唯一东西就是指向该对象的指针,这样对象本身就不会被复制。如果要在函数中像双指针一样切换或创建新对象,可以使用ref或out。
简短的回答:阅读我关于论点传递的文章。
长回答:当引用类型参数按值传递时,只传递引用,而不传递对象的副本。这就像在C或C++中传递一个指针(按值)。调用者不会看到对参数本身值的更改,但会看到引用指向的对象中的更改。
当通过引用传递参数(任何类型)时,这意味着调用方可以看到对参数的任何更改-对参数的更改是对变量的更改。
当然,这篇文章更详细地解释了这一切:)
有用的答案:你几乎不需要使用ref/out。这基本上是获取另一个返回值的一种方法,通常应该避免使用,因为这意味着该方法可能做得太多了。这并不总是如此(TryParse等是合理使用out的典型例子),但使用ref/out应该是相对罕见的。
- 好文章,乔恩。
- 我想你的简短回答和冗长回答混在一起了,这是一篇大文章!
- @违法者:是的,但简短的回答本身,即阅读文章的指示,只有6个字长:)
- 仅供参考!:)
- +所有的好东西。我喜欢使用ref来明确表示我希望这个对象被用作引用对象,也就是说,我可能会改变其中的某些内容。只要找到它就可以使代码更可读。
- 约翰,你的链接断了
- @埃文拉森:它对我有效-当你尝试它时会发生什么?它应该重定向到yoda.arachsys.com/csharp/parameters.html。
- joh,这就是它向我展示的:拒绝访问(内容过滤器被拒绝)您的请求因其内容分类而被拒绝:"电子邮件"如需帮助,请与您的网络支持团队联系。
- 不过我在谷歌上找到的!谢谢
- @埃文拉森:是的-这不是网页或链接被破坏,这是你互联网连接上的一个过滤器。可能是公司代理人?
- 是的,我在用公司的互联网,所以……它可能是出于某种未知的原因而过滤它。
- @Liam像你一样使用Ref可能会让你觉得更明确,但它实际上可能会让其他程序员(那些知道该关键字做什么的人)感到困惑,因为你本质上是在告诉潜在的调用者,"我可以修改你在调用方法中使用的变量,也就是说,将它重新分配给一个不同的对象(如果可能的话甚至是空的),所以坚持住它,或者确保在我完成它后你验证它"。这很强,与"这个对象可以修改"完全不同,当您将对象引用作为参数传递时,总是这样。
- @Jonskeet在什么情况下应该使用Ref?
- @当你想要这些语义时,基本上…这应该是非常罕见的,依我看。它有点突出,而且让一些事情更痛苦(例如,你必须有一个完全正确类型的变量)。
- @manirazss我主要使用它来避免从助手函数返回"新"对象(特别是在我没有编写被操纵的类时)。对new的调用越少,垃圾收集就越少。从本质上讲,它有助于互操作性。
- @琼斯基特:好文章。从第一次读者的角度来看,"检查你是否理解序言……"这一节很棒。但是,我发现自己在阅读示例代码时用手覆盖了输出。伟大的文章。
- @这篇文章对我很有帮助。它帮助我理解我已经知道的事情(如果这有意义的话!!!)好文章!
- @在这个可以为空类型的时代,难道TryParse不应该用nullable类型代替out parameter吗?即当解析失败时,返回null,否则返回类型。
- @M.Mimpen:我想如果它是今天设计的,那就应该是这样了——但是现在这种模式已经成为惯例了。
- 斯基特先生建议的这篇文章是迄今为止最好的答案!
- @M.Mimpen我不认为可以为空。try语义对于if块很有用。如果返回了可以为空的,则必须将其赋给变量,并检查if块中是否存在variable.hasValue。使用try,如果try成功,您只需进入内部,还可以将out变量作为inscope和populated。
- @mpavlak检查这个:i.imgur.com/2admcd.jpg如果你不习惯尝试语义,这是非常自然的。两个结构的"inscope和populated"部分都适用。
- @M.MIMPEN:C 7将(希望)允许if (int.TryParse(text, out int value)) { ... use value here ... }。
- @我会很期待的,那太好了!
- 如果(int.triparse("5")让我)//我有解析值,并且仅在这里是不范围的
- @m.mimpen那么您将如何解析一个可以为空的int?重点是函数的返回值是它是否成功。使用这种方法,您将不得不抛出一个异常来通知解析失败,而不是它解析为空。
- @我不明白你的意思。int?.TryParse不存在,所以你也必须对此作出补偿。你能给我们一个你想要这个能力的真实世界吗?
- @m.mimpen重点是如何判断函数是否成功,而不是观察函数的返回值。如果我想将一个可以为空的int编码为"null"或"1"、"2"…如何在没有try模式的情况下解析这个back out?
- 但是,如果调用方不希望在任何情况下修改结构,那么传递对大型结构的引用是否比复制完整结构更好?
- @奥斯汀:这就是C 7.2中的参数。但是,别名带来了风险——如果您以这种方式传入一个字段,该方法可以很容易地观察到它没有预料到的更改。是的,这可能会带来性能方面的好处,但可能会以能够轻松解释代码为代价。
把非引用参数看作指针,把引用参数看作双指针。这对我帮助最大。
您几乎不应该按引用传递值。我怀疑如果不是出于互操作方面的考虑,.NET团队将永远不会在原始规范中包含它。处理参考参数所解决的大多数问题的OO方法是:
对于多个返回值
对于因方法调用而在方法中更改的基元(方法对基元参数有副作用)
- 将对象中的方法实现为实例方法,并作为方法调用的一部分操作对象的状态(而不是参数)
- 使用多返回值解决方案并将返回值合并到您的状态
- 创建一个包含可由方法操作的状态的对象,并将该对象作为参数传递,而不是将原语本身传递给原语。
- 上帝啊!我得看20倍才能理解。听起来像是为了做一些简单的事情而做的额外工作。
- .NET框架不遵循您的1规则。('对于多个返回值,创建结构')。以IPAddress.TryParse(string, out IPAddress)为例。
- @你说得对。我假设在大多数使用参数的地方,(a)他们正在包装Win32 API,或者(b)它是早期的,C++程序员正在做决定。
- @我知道这个答复迟了很多年,但确实迟了很多年。我们习惯于胰蛋白酶,但这并不意味着它是好的。如果我们有var candidate = int.TrialParse("123"); if (candidate.Parsed) { /* do something with candidate.Value */ },而不是if (int.TryParse("123", out var theInt) { /* use theInt */ },情况会更好。它更多的是代码,但更符合C语言设计。
您可能会编写一个完整的C应用程序,并且永远不会按引用传递任何对象/结构。
我有个教授告诉我:
The only place you'd use refs is where you either:
Want to pass a large object (ie, the objects/struct has
objects/structs inside it to multiple levels) and copying it would
be expensive and
You are calling a Framework, Windows API or other API that requires
it.
Don't do it just because you can. You can get bit in the ass by some
nasty bugs if you start changing the values in a param and aren't
paying attention.
我同意他的建议,在我上学五年多的时间里,除了调用框架或WindowsAPI之外,我从来没有需要过它。
- 如果您计划执行"交换",那么通过引用传递可能会有所帮助。
- @克里斯,如果我对小物体使用ref关键字会有什么问题吗?
- @technikEmpire"但是,对被调用函数范围内的对象所做的更改不会反映回调用方。"这是错误的。如果我将某人传递给SetName(person,"John Doe"),则名称属性将更改,并且该更改将反映给调用方。
- @m.mimpen注释已删除。当时我几乎还没学会C,很明显我在说我的事。谢谢你提醒我。
- @克里斯-我很肯定通过一个"大"物体并不昂贵。如果你只是通过值传递它,你仍然只是传递一个指针,对吗?
把ref想象成一个引用传递一个指针。不使用ref意味着您正在按值传递指针。
更好的是,忽略我刚才所说的(这可能会产生误导,尤其是对于值类型),并阅读这个msdn页面。
- 事实上,不是真的。至少第二部分。无论是否使用ref,任何引用类型都将始终通过引用传递。
- 事实上,经过进一步的思考,这个结果并不正确。引用实际上是通过不带引用类型的值传递的。也就是说,更改引用指向的值会更改原始数据,但更改引用本身不会更改原始引用。
- 非引用引用类型不是通过引用传递的。对引用类型的引用按值传递。但是如果你想把一个引用看作是指向某个被引用的东西的指针,我说的话是有意义的(但是这样想可能会误导你)。因此我发出警告。
因为接收到的是一个数组,所以您将向该数组传递一个指针。函数操作现有数据,而不更改基础位置或指针。ref关键字表示您正在将实际指针传递到位置,并在外部函数中更新该指针,因此外部函数中的值将更改。
例如,字节数组是指向同一内存的指针,在此之前和之后,内存刚刚更新。
端点引用实际上正在将指向外部函数中端点的指针更新为在函数内部生成的新实例。
虽然我完全同意Jon Skeet的答案和其他一些答案,但有一个使用ref的用例,这是为了加强性能优化。在性能分析过程中观察到,设置一个方法的返回值对性能有轻微的影响,而使用ref作为参数,在该参数中填充返回值会导致消除这个轻微的瓶颈。
只有在优化工作达到极端水平时,这才真正有用,因为这样做会牺牲可读性,可能会牺牲可测试性和可维护性,从而节省毫秒或拆分毫秒。
我的理解是,从对象类派生的所有对象都作为指针传递,而普通类型(int,struct)不作为指针传递,需要引用。我对字符串(它最终是从对象类派生的吗?)
- 这可能需要一些澄清。价值观和参考类型之间的区别是什么?ans为什么这与在参数上使用ref关键字相关?
- 在.NET中,除了指针、类型参数和接口之外的所有内容都是从对象派生的。重要的是要理解,"普通"类型(正确地称为"值类型")也继承自对象。从这点上说是对的:值类型(默认情况下)是按值传递的,而引用类型是按引用传递的。如果您想要修改一个值类型,而不是返回一个新的值类型(像方法中的引用类型一样处理它),那么您必须在它上面使用ref关键字。但这是不好的风格,你不应该这样做,除非你绝对确定你必须这样做:)
- 回答您的问题:字符串是从对象派生的。它是一个引用类型,其行为类似于值类型(出于性能和逻辑原因)。
基本零规则首先,在涉及类型的上下文中,原语按值(堆栈)和非原语按引用(堆)传递。
默认情况下,所涉及的参数按值传递。很好的文章能详细解释问题。网址:http://yoda.arachsys.com/csharp/parameters.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| 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
} |
我们可以说(带有警告)非原语类型只是指针当我们通过裁判时,我们可以说我们是通过了双指针
- 默认情况下,所有参数都以C形式按值传递。任何参数都可以通过引用传递。对于引用类型,传递的值(通过引用或通过值)本身就是引用。这完全独立于它是如何通过的。
- 同意@servy!"当我们听到"引用"或"值"这两个词时,我们应该非常清楚地记住,我们的意思是参数是引用参数还是值参数,或者我们的意思是所涉及的类型是引用类型还是值类型,在我这方面有点困惑,当我首先说零基规则时,原语是通过值(堆栈)和非pri传递的。mitive by reference(heap)我指的是相关类型,而不是参数!当我们讨论参数时,您是正确的,所有参数都是按值传递的,默认情况下在C中。
- 说一个参数是一个引用或值参数不是一个标准术语,对于您的意思来说,这是非常含糊的。参数的类型可以是引用类型或值类型。任何参数都可以通过值或引用传递。这些是任何给定参数的正交概念。你的文章没有正确地描述这一点。
- 对不起,我不同意这个@服务!by-ref(堆)或by-val(堆栈)只是描述变量性质的标准关键字。所涉及的变量是参数变量还是正态变量无关紧要。
- 通过引用传递参数与堆无关。当通过引用传递参数时,甚至不使用堆。堆栈本身用于这样的操作,例如当参数通过值传递时。区别仅仅在于变量是否分配了自己的堆栈空间,或者参数标识符的所有使用是否与堆栈中的某个现有位置对应。
- 如果我们调用函数堆栈是唯一正在使用的东西,但是堆栈中的变量总是可以引用堆。在C 35;中,变量&35;173;变量可根据其类型存储在堆栈或堆中:val&173;ue类型放在堆栈中,而参考 173;er&35173;ence类型放在堆结构中,C 是一种值类型,如枚举,因此两者都放在堆栈中,即记录& 173;OM 35; 173;修改(对于赌注 173;ter per 173;对于\ 35; mance)将结构类型预先传递给ref&173;er&173;ence类型,适用于主要用于存储数据的小型对象。更多详情请访问:burningmonk.com/2010/02/…
- 那完全是错误的。首先,正如我前面所说,在讨论某个特定参数是通过引用还是通过值传递时,这完全无关。此外,值类型不一定会出现在堆栈上。您对是否使用值或引用类型的决定也相当差。当所表示的类型在概念上表示单个值时,应该使用值类型。对于大多数较新的用户(显然包括您),处理值类型也是很难理解的,因此通常应避免这种情况,直到完全理解它们为止。
- 你引用的这篇文章中充满了错误的/误导性的/通常是不好的内容。你不应该相信或依赖它说的任何话。
- 罗杰!但是像杰弗里·里克特这样的人说你必须相信。msdn.microsoft.com/en-us/magazine/cc301569.aspx
- 我想我们是从不同的角度思考的。我是在类型上下文中由引用和val讨论的。你关注的是参数。传递给函数的任何内容最终都会被推送到堆栈中。现在,这取决于一个人如何通过它,以及通过了什么。
- 但是你的回答和评论与那篇文章的内容不一致。我没有通读整件事,但它看起来是一篇不错的文章。如果你的准确度代表了这些点,那么你可能有一个好的帖子。
- 通过引用或值传递参数。术语"按引用"和"按值"不用于描述类型是值类型还是引用类型。
- 可能是这样,因为我现在很困!你也帮我把它钉住了!""ref"或"value"和"by ref"或"by value"在不同的上下文中使用。在参数中使用,在类型中使用!当涉及到概念时,它取决于很多东西(它们是你描绘计算机和其他事物的方式)
- 你已经做了很多完全错误的陈述,并且可以证明是这样的。在许多情况下,您使用了不适当的术语来指代特定的概念;这会导致语句最坏、完全错误,最多甚至是高度误导。实际上,您在这里所说的任何内容都不能准确地描述您声称它们描述的概念。
- 诺普斯!这只是感知的问题。我是从类型的角度谈的。
- 不,这不是感知问题。当你用不正确的词来指某件事时,那句话就错了。正确的陈述必须使用正确的术语来引用概念。
- 酷里奥:)谢谢@survy:)下一次我会更警惕的:)和你一起学习真好!