是否可以将属性作为“out”或“ref”参数传递?

Posted

技术标签:

【中文标题】是否可以将属性作为“out”或“ref”参数传递?【英文标题】:Is it possible to pass properties as "out" or "ref" parameters? 【发布时间】:2010-10-08 13:14:09 【问题描述】:

如果不能,我可以将属性作为“out”或“ref”参数传递,那为什么不呢?

例如

Person p = new Person(); 

。 . .

public void Test(out p.Name);

【问题讨论】:

请重新输入您的问题以使其有意义... 它得到了一个明智的答案就足够有意义了 嗨,实际上我的问题是:如果不是,我可以将属性作为“out”或“ref”参数传递吗?例子。人 p = 新人(); public void Test(out p.Name); 【参考方案1】:

其他人已经解释说您不能在 C# 中执行此操作。在 VB.NET 中,您可以执行此操作,即使使用选项 strict/explicit on:

Option Strict On
Option Explicit On
Imports System.Text

Module Test

   Sub Main()
       Dim sb as new StringBuilder
       Foo (sb.Length)
   End Sub
   
   Sub Foo(ByRef x as Integer)
   End Sub
    
End Module

以上代码等价于这段C#代码:

using System.Text;

class Test

     static void Main()
     
         StringBuilder sb = new StringBuilder();
         int tmp = sb.Length;
         Foo(ref tmp);
         sb.Length = tmp;
     

     static void Foo(ref int x)
     
     

就我个人而言,我很高兴 C# 没有这个功能 - 它非常混乱,尤其是在属性的值方面,如果在方法中设置了参数但随后引发了异常。

编辑:根据要求,我关于为什么我认为在水中传递财产的理由。如果您通过引用传递一个普通变量,那么每次在方法中引用它时都会评估该变量。如果值因某种原因发生变化(例如,作为方法中某些其他工作的副作用),则该更改将立即在方法中可见。如果您在 VB.NET 中通过引用传递属性,则情况并非如此:属性 getter 被调用一次,然后属性 setter 被调用一次。这不像您要传入“这是一个 property - 每当您使用参数时都可以从中获取和设置。”

这是一个完整的示例,其中在 .NET 中传递字段和传递完全无关紧要的属性会产生截然不同的结果:

Option Strict On
Option Explicit On
Imports System.Text

Class Test

   Dim counter as Integer
   
   Property CounterProperty As Integer
       Get
           Return counter
       End Get
       Set (ByVal value as Integer)
           counter = value
       End Set
   End Property
   
   Sub Increment
       counter += 1
   End Sub

   Shared Sub Main()
       Dim t as new Test()
       Console.WriteLine("Counter = 0", t.counter)
       t.Foo(t.counter)
       Console.WriteLine("Counter = 0", t.counter)

       t.CounterProperty = 0
       Console.WriteLine("CounterProperty = 0", t.CounterProperty)
       t.Foo(t.CounterProperty)
       Console.WriteLine("CounterProperty = 0", t.CounterProperty)
   End Sub
   
   Sub Foo(ByRef x as Integer)
       x = 5
       Increment
       Increment
       Increment
       x += 1
   End Sub
    
End Class

.Net-Fiddle:https://dotnetfiddle.net/ZPFIEZ(字段和属性的结果不同)

【讨论】:

Jon:如果你不介意,你能不能解释一下你对“搅浑水”的想法,我不明白为什么你认为将属性传递给 ref 参数是一个特别糟糕的主意(我'不是说他们是个好主意)。谢谢老兄。【参考方案2】:

为简短的回答道歉,但不,C# 语言规范不允许这样做。

看到这个answer 到另一个问题,看看当你尝试时会发生什么。 它还说明了为什么您不应该将属性设置为公共字段以规避限制。

希望对你有帮助

编辑:你问为什么?

您将变量传递给outref 参数实际上是在传递变量的地址(或内存中的位置)。 在函数内部,编译器知道变量的真正位置,并获取并将值写入该地址。

属性看起来像一个值,但它实际上是一对函数,每个函数都有不同的签名。因此,要传递一个属性,您实际上需要传递两个函数指针,一个用于获取,一个用于设置。

传递给函数的东西与传递变量的地址完全不同

即一个变量地址v的两个函数指针。

更新为什么 C# 不为我们解决这个问题?

我不是Eric Lippert,但我会试一试为什么

您要调用的函数的签名应该是什么? 假设您想打电话给void MyFn(ref int i),应该保持这种状态,还是应该改为说我们也允许属性?如果它更改为 void MyFn(prop_ref int i) 之类的语法,那么这将毫无用处,您无法将属性传递给库函数或未使用特殊 prop_ref 修饰符编写的第 3 方代码。无论如何,我认为您的建议不应该有所不同。

现在假设MyFni 传递给COM 函数或WinAPI 调用,传递i 的地址(即在.net 外部,通过引用)。如果是房产,如何获得i的地址?属性下可能没有实际的 int 来获取地址。你做 VB.Net 做的事吗?

当属性作为 ByRef 参数传递给方法时,Vb.Net 编译器会发现。此时它声明了一个变量,将属性复制到变量中,通过引用传递变量,然后在调用方法之后,将变量复制回属性中。即

MyFunc(myObject.IntProperty)

变成

Dim temp_i As Integer = myObject.IntProperty
MyFunc(temp_i)
myObject.IntProperty = temp_i

MyFunc 返回之前不会发生任何属性副作用,这可能会导致各种问题并导致非常微妙的错误。

在我看来,这个问题的 Vb.Net 解决方案也被破坏了,所以我不会接受它作为答案。

你认为 C# 编译器应该如何处理这个问题?

【讨论】:

这个坏了。 C# 不是低级语言,所以ref 参数不应该是地址,而实际上是一对闭包,一个getter 和一个setter。如果您将局部变量作为ref 参数传递,该语言应自动创建getter/setter 对。而这一切都应该对毫无戒心的程序员隐藏起来。 @EduardoLeón:有很多事情可以用地址完成,而不能用闭包完成。允许将属性作为引用传递的正确方法是定义一种属性,该属性不包含 getter 和 setter,而是以受控方式公开地址(例如,类型 T 的属性可以有一个方法,给定一个接受 T 类型的 ref 参数的委托,会将该属性的支持字段(或 T 类型的其他存储位置)的地址传递给该委托。 一个属性严格等同于一对get/set函数。事实就是这样。因此(正如@EduardoLeón 指出的那样)所有有用的东西都可以被两个闭包捕获。我在 Roslyn 做了这样的原型:smellegantcode.wordpress.com/2014/04/27/… @supercat 虽然更简单的增强是让您在自动属性上使用 ref。编译器会生成它们的支持字段,因此它可以让 ref 在支持字段上工作。 @EduardoLeón:“所有这些都应该对毫无戒心的程序员隐藏。”目前ref 参数保证没有副作用。例如,一个方法可能会在返回之前多次使用+= 附加到ref string,可能会随时检查值。该方法的程序员需要知道它的参数是否支持属性,因为在这种情况下,他们需要创建一个临时变量。【参考方案3】:

相反,您应该这样做

WhatEverTheType name;

Test(out name);

// Choose one of the following construction

Person p = new Person();
p.Name = name;

Person p = new Person(name);
Person p = new Person(Name => name);

【讨论】:

以上是关于是否可以将属性作为“out”或“ref”参数传递?的主要内容,如果未能解决你的问题,请参考以下文章

属性或索引器不能作为 out 或 ref 参数传递

属性、索引器或动态成员访问不能作为 out 或 ref 参数传递[重复]

c#面试问题及答案

ref和out实际用法

out 和 ref + params 的理解

ref的使用