72

我在使用 C# 时遇到了这个错误消息

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

我知道是什么原因造成的,并快速解决了创建正确类型的局部变量,使用它作为out/ref参数调用函数,然后将其分配回属性:

RefFn(ref obj.prop);

变成

{
    var t = obj.prop;
    RefFn(ref t);
    obj.prop = t;
}

显然,如果属性不支持在当前上下文中获取和设置,这将失败。

为什么 C# 不为我这样做?


我能想到这可能会导致问题的唯一情况是:

  • 穿线
  • 例外

对于线程,转换会影响写入发生的时间(在函数调用之后与函数调用中),但我宁愿怀疑任何依赖于此的代码在中断时都会得到很少的同情。

对于例外情况,关注点是;如果函数分配给几个ref参数之一而不是 throws 会发生什么?任何微不足道的解决方案都会导致分配所有参数或不分配参数,什么时候应该分配,有些不应该分配。同样,我不认为这会被支持使用该语言。


注意:我了解生成此错误消息的机制。我正在寻找的是为什么 C# 不会自动实现微不足道的解决方法的基本原理。

4

9 回答 9

31

因为您正在传递索引器的结果,这实际上是方法调用的结果。不能保证 indexer 属性也有一个 setter,当开发人员认为他的属性将在没有调用 setter 的情况下被设置时,通过 ref 传递它会导致开发人员的错误安全。

在更技术层面上, ref 和 out 传递传递给它们的对象的内存地址,并且要设置属性,您必须调用 setter,因此不能保证属性实际上会被更改,尤其是当属性类型为不可变的。ref 和 out 不只是在方法返回时设置值,它们将实际的内存引用传递给对象本身。

于 2009-02-09T20:34:47.407 回答
16

属性只不过是 Java 风格的 getX/setX 方法的语法糖。对于方法上的“ref”没有多大意义。在您的实例中,这是有道理的,因为您的属性只是存根字段。属性不必只是存根,因此框架不能允许在属性上“引用”。

编辑:嗯,简单的答案是,属性获取器或设置器可以包含的不仅仅是一个读/写字段,这使得它不受欢迎,更不用说可能出乎意料,允许你提出的那种糖。这并不是说我以前不需要这个功能,只是我理解他们为什么不想提供它。

于 2009-02-09T20:36:05.080 回答
12

仅供参考,C# 4.0类似ref这种糖的东西,但仅在调用互操作方法时 - 部分原因是在这种情况下的纯粹倾向。我没有对它进行太多测试(在 CTP 中);我们得看看结果如何……

于 2009-02-09T20:39:11.030 回答
8

ref您可以使用带有/的字段out,但不能使用属性。原因是属性实际上只是特殊方法的语法捷径。编译器实际上将 get/set 属性转换为相应的get_Xset_X方法,因为 CLR 没有立即支持属性。

于 2009-02-09T20:33:57.563 回答
6

它不是线程安全的;如果两个线程同时创建自己的属性值副本并将它们作为 ref 参数传递给函数,则只有一个线程最终返回属性中。

class Program
{
  static int PropertyX { get; set; }

  static void Main()
  {
    PropertyX = 0;

    // Sugared from: 
    // WaitCallback w = (o) => WaitAndIncrement(500, ref PropertyX);
    WaitCallback w = (o) => {
      int x1 = PropertyX;
      WaitAndIncrement(500, ref x1);
      PropertyX = x1;
    };
    // end sugar

    ThreadPool.QueueUserWorkItem(w);

    // Sugared from: 
    // WaitAndIncrement(1000, ref PropertyX);
    int x2 = PropertyX;      
    WaitAndIncrement(1000, ref x2);
    PropertyX = x2;
    // end sugar

    Console.WriteLine(PropertyX);
  }

  static void WaitAndIncrement(int wait, ref int i)
  {
    Thread.Sleep(wait);
    i++;
  }
}

PropertyX 最终为 1,而字段或局部变量为 2。

该代码示例还强调了在要求编译器执行含糖操作时,匿名方法等引入的困难。

于 2009-02-09T21:46:27.470 回答
5

这样做的原因是 C# 不支持接受通过引用传递的参数的“参数化”属性。有趣的是,CLR 确实支持此功能,但 C# 不支持。

于 2009-02-09T20:41:55.847 回答
4

当您传递 ref/out 前缀时,这意味着您正在传递存储在堆中的引用类型。

属性是包装方法,而不是变量。

于 2009-02-09T20:35:23.803 回答
0

如果您问为什么编译器不替换属性的 getter 返回的字段,那是因为 getter 可以返回 const 或 readonly 或文字或不应重新初始化或覆盖的其他内容。

于 2009-02-09T20:40:10.093 回答
0

该站点似乎为您提供了解决方法。不过我还没有测试过,所以我不能保证它会起作用。该示例似乎使用反射来访问属性的 get 和 set 函数。这可能不是推荐的方法,但它可能会完成您的要求。

http://www.codeproject.com/KB/cs/Passing_Properties_byref.aspx

于 2009-02-09T20:40:42.260 回答