C#属性和ref参数,为什么不加糖?


71

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

属性或索引器可能无法作为out或ref参数传递

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

RefFn(ref obj.prop);

变成

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

显然,如果该属性在当前上下文中不支持get和set,则此操作将失败。

为什么C#不能为我做到这一点?


我可以想到的地方可能是唯一的情况是:

  • 穿线
  • 例外情况

对于线程而言,这种转换会影响写入发生的时间(在函数调用之后还是在函数调用之后),但是我宁愿怀疑依靠它的任何代码在中断时都不会产生同情。

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


注意:我了解产生此错误消息的机制。我要寻找的是C#不能自动实现琐碎变通办法的理由。


5
通往含糖编译器的道路铺就了良好的意图。
公元前。

它没有解决方法,因为它并不总是安全的。更根本的问题是类型的属性T不仅应具有getter和setter方法,还应具有ActUpon<U>([indexparams...,] ActionByRef<T,U> act, ref U param)方法。这样的方法将允许ListOfPoints[4].X += something使用静态委托而不使用闭包(ListOfPoints.ActUpon(4, (ref Point it, ref param) => {it.X += param;}, ref something);)来高效地执行一条语句。
超级猫

在此答案底部使用表达式来解决此问题的可靠方法:stackoverflow.com/a/3059448/176877
克里斯·莫斯基尼

Answers:


12

仅出于信息方面的考虑,C#4.0带有类似的糖,但仅在调用互操作方法时会ref出现这种情况-部分是由于这种情况下的纯粹倾向。我(在CTP中)没有做过很多测试;我们将不得不看它如何成功...



1
“特别是对于COM方法,C#编译器将允许您通过值将参数传递给此类方法,并将自动生成临时变量来保存传入的值,随后在调用返回时将其丢弃。”
Marc Gravell

C#不是低级语言,因此ref参数不应是地址,而是实际上是一对闭包,一个getter和一个setter。如果将局部变量作为ref参数传递,则语言应自动创建一个getter / setter对。所有这些都应该对毫无戒心的程序员隐藏起来
pyon

改变语义的@Eduardo正在打破方式,尤其是对于价值类型而言;那将永远不会发生。我不想这么做
Marc Gravell

@Marc:那将如何改变任何事物的语义?毕竟,变量只是一种读取(获取)和写入(设置)到一堆内存的机制。
pyon 2011年

32

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

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


1
+1,需要特别注意的是P / Invoke可能会阻塞,因为该属性所属的对象可能不再具有GC根并被收集!Doh ...
user7116

@sixletter:不是这样,对于要收集的对象,必须删除调用代码中对其的引用。
BCS

2
您所描述的问题将解决其自身问题,就像Sugar在编译时会尝试解决setter并失败,并出现与尝试分配给get Only属性相同的错误。
BCS

1
是的,但是请记住,C#属性本身就是语法糖。人们已经感到困惑,因为财产看起来就像一块田地。当然,添加更多语法糖绝对不会帮助解决此问题。有并且应该是一个极限。
大卫·莫顿

如果属性是字符串,则属性的结果是指向该字符串的指针。ref是指向该指针的指针。因此,不管它是否是属性都无关紧要,它应该修正引用。此修复程序已完成,并且在VB.net中工作正常,似乎缺少C#。没错,当作为引用传递给setter时,它应该被调用,并且不能保证该属性实际上会被更改。可以保证您的安装程序代码将运行。这在VB.NET中也可以验证。
Brain2000

17

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

编辑:好吧,简单的答案是,属性获取器或设置器可能包含的内容远不止是字段读/写,这使它不受欢迎,更不用说出乎意料了,允许您提议的糖类。这并不是说我以前不需要此功能,只是我了解他们为什么不想提供此功能。


+1这是唯一不需要5次阅读即可理解的解释。
goku_da_master

9

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


1
我知道,请参阅我的新笔记。
BCS

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。

该代码示例还突显了当要求编译器执行含糖任务时,诸如匿名方法之类的操作所带来的困难。


很好,但是我认为您所展示的是,转换可以更改已经具有竞争条件的代码的结果,例如非线程安全代码。
BCS

是的,在我给出的示例中。哎呀。
Mark Rendle

恕我不能赞同。上面的示例仅显示x1和x2都设置为0,然后在500和1000毫秒的周期后都将其递增为1,然后都将PropertyX设置为1。两个局部变量都不是2。需要将PropertyX传递给WaitAndIncrement()才能进行真正的测试,这只能在VB.NET中完成。但是,它可能仍然无法正常工作,因为首先会调用吸气剂。它与线程安全无关。与使用没有任何互锁功能的局部变量一样,线程安全也不亚于安全。
Brain2000

再次仔细阅读我的答案,您会发现您完全不同意我的看法。
马克·雷德尔

4

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


4
VB.net支持它,我对C#为什么不支持感到困惑。
Brain2000

如果上述说法属实,谁能回答?
Coops


1

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


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.