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

时间:2009-02-19 09:43:27

标签: c# arguments pass-by-reference

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

e.g。

Person p = new Person(); 

。 。

public void Test(out p.Name);

4 个答案:

答案 0 :(得分:38)

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

请参阅此answer另一个问题,了解您尝试时会发生什么。 它还说明了为什么你不应该让这个属性成为一个公共领域来解决限制。

希望这有帮助

编辑:你问为什么?

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

属性看起来像一个值,但它实际上是一对函数,每个函数都有不同的签名。所以要传递一个属性,你实际上需要传递两个函数指针,一个用于get,另一个用于set。

传递给函数而不是变量

的地址是完全不同的

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

<强>更新
为什么C#不为我们照顾这个?

我不是Eric Lippert,但我会说明原因

您所使用的功能的签名应该是什么?
假设您想要保留void MyFn(ref int i),如果仍然如此,或者是否应该更改为说我们也允许属性?如果它改为某些语法,如void MyFn(prop_ref int i),则这是相当无用的,您无法将属性传递给库函数或未使用特殊prop_ref修饰符编写的第三方代码。无论如何,我认为你建议不应该有所不同。

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

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

MyFunc(myObject.IntProperty)

变为

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

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

我谦虚地认为Vb.Net解决这个问题的方法也被打破了,所以我不会接受这个作为答案。

您认为C#编译器应如何处理?

答案 1 :(得分:12)

其他人已经解释过你不能在C#中做到这一点。在VB.NET中,可以执行此操作,即使使用选项strict / explicit:

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被调用一次。这不像你传入“这里是一个属性 - 每当你使用参数时从中得到并设置。”

这是一个完整的例子,在.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

答案 2 :(得分:2)

不允许这样做的另一个原因是因为ref和out参数在方法内是可读写的,而属性可以是readonly / writeonly。

Person
{
    public string Name { get { return "me"; } }
}

现在如果你能做到这一点怎么办?

Test(out p.Name);    

public void Test(out string name)
{
    name = "someone else";
}

您现在不是您,而是其他人,但这违反了您使用get仅与Name属性签订的合同(如果这有效)。对于类的只读成员字段,情况也是如此,您无法传递它们的引用。

Person
{
    public readonly string name = "me";
}

Test(out p.name); //not possible.

可能C#可以为方法提供readonly / writeonly参数:

public void Test(out settable string name, gettable int count, bool whatever)
{
    name = "someone else";
}

Test(out p.Name, 0, true); // doesnt compile since p.Name is readonly.

答案 3 :(得分:0)

相反,你应该做这样的事情

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);