【问题标题】:C# property and ref parameter, why no sugar?C# 属性和 ref 参数,为什么不加糖?
【发布时间】:2010-10-06 11:58:16
【问题描述】:

我刚刚在使用 C# 时遇到此错误消息

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

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

RefFn(ref obj.prop);

变成

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

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

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


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

  • 线程
  • 例外情况

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

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


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

【问题讨论】:

  • 通向含糖编译器的道路是用善意铺就的。
  • 它不能解决问题,因为它并不总是安全的。更根本的问题是T 类型的属性不应该只有getter 和setter 方法,还应该有ActUpon<U>([indexparams...,] ActionByRef<T,U> act, ref U param) 方法。这样的方法将允许使用静态委托而不使用闭包 (ListOfPoints.ActUpon(4, (ref Point it, ref param) => {it.X += param;}, ref something);) 有效地执行像 ListOfPoints[4].X += something 这样的语句。
  • 使用此答案底部的表达式解决此问题的可靠方法:stackoverflow.com/a/3059448/176877

标签: c# properties syntactic-sugar reference-parameters


【解决方案1】:

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

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

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

    • +1 这是唯一不需要阅读5遍就能理解的解释。
    【解决方案3】:

    它不是线程安全的;如果两个线程同时创建自己的属性值副本并将它们作为 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。

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

    【讨论】:

    • 好点,但我认为您所展示的是转换可以改变已经具有竞争条件的代码的结果,例如非线程安全代码。
    • 这是真的,在我给出的例子中。哎呀。
    • 我不同意你的观点。上面的例子简单地显示了 x1 和 x2 都设置为 0,然后在 500 和 1000 毫秒的周期后都递增到 1,然后它们都将 PropertyX 设置为 1。这两个局部变量都不是 2。需要将 PropertyX 传递给 WaitAndIncrement() 才能真正测试这一点,这只能在 VB.NET 中完成。但是,它可能仍然不起作用,因为首先调用了 getter。它与线程安全无关。它的线程安全性不亚于使用没有任何互锁函数的局部变量。
    • 再仔细阅读我的回答,你会发现你根本不同意我的观点。
    【解决方案4】:

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

    【讨论】:

    • VB.net 支持它,我不明白为什么 C# 不支持。
    • 如果以上属实,谁能回答这个问题?
    【解决方案5】:

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

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

    【讨论】:

    • 啊,使用反射没有错。它有时只是为您解决问题。
    【解决方案6】:

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

    【讨论】:

      【解决方案7】:

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

      【讨论】:

      • "特别是对于 COM 方法,C# 编译器将允许您按值将参数传递给此类方法,并会自动生成临时变量来保存传入的值,随后在调用时丢弃这些返回。"
      • C# 不是低级语言,所以 ref 参数不应该是地址,而实际上是一对闭包,一个 getter 和一个 setter。如果您将局部变量作为 ref 参数传递,则该语言应自动创建 getter/setter 对。而这一切都应该对毫无戒心的程序员隐藏
      • @Eduardo 改变语义的做法正在打破常规,尤其是对于值类型;那永远不会发生。我不希望它发生
      • @Marc:这将如何改变任何事物的语义?毕竟,变量只是一种读取(获取)和写入(设置)到一堆内存的机制。
      【解决方案8】:

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

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

      【讨论】:

      • +1,重要的是要注意 P/Invoke 可能会阻塞,因为该属性所属的对象不再具有 GC 根并被收集!呵呵……
      • @sixletter:不是这样,要收集对象,必须删除调用代码中对它的引用。
      • 您描述的问题会自行解决,因为糖会在编译时尝试解析 setter 并失败,并出现与尝试分配给 get only 属性相同的错误。
      • 是的,但请记住,C# 属性本身就是语法糖。人们已经感到困惑,因为属性看起来就像一个字段。添加更多的语法糖肯定不会帮助解决这个问题。存在并且应该存在限制。
      • 如果属性是字符串,则属性的结果是指向该字符串的指针。 ref 是指向该指针的指针。所以它是否是一个属性并不重要,它应该修复引用。这个修复已经完成并且在 VB.net 中工作得很好,它似乎缺少 C#。你是对的,当作为 ref 传递时,setter 应该被调用,并且不能保证该属性会实际更改。可以保证您的设置器代码将运行。这也可以在 VB.NET 中验证。
      【解决方案9】:

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

      【讨论】:

      • 我知道,请看我的新笔记。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-03-16
      • 2014-01-07
      • 2017-10-20
      • 2010-11-23
      • 1970-01-01
      • 2013-10-15
      • 2010-11-15
      相关资源
      最近更新 更多