【问题标题】:Interesting "params of ref" feature, any workarounds?有趣的“参考参数”功能,有什么解决方法吗?
【发布时间】:2009-11-21 16:55:05
【问题描述】:

我想知道对于值类型是否有任何可能的方式......

public static class ExtensionMethods {
    public static void SetTo(this Boolean source, params Boolean[] bools) {
        for (int i = 0; i < bools.Length; i++) {
            bools[i] = source;
        }
    }
}

那么这是可能的:

Boolean a = true, b, c = true, d = true, e;
b.SetTo(a, c, d, e);

当然,这不起作用,因为 bool 是值类型,所以它们作为值而不是引用传递给函数。

除了将值类型包装成引用类型(通过创建另一个类)之外,还有什么方法可以在使用 params 修饰符时通过引用(ref)将变量传递给函数?

【问题讨论】:

    标签: c# reference extension-methods params


    【解决方案1】:

    这是不可能的。为了解释原因,首先阅读我的文章,为什么我们通过将值类型的局部变量放入堆栈来优化它们的释放:

    https://web.archive.org/web/20100224071314/http://blogs.msdn.com/ericlippert/archive/2009/05/04/the-stack-is-an-implementation-detail-part-two.aspx

    既然您已经理解了这一点,那么您应该清楚为什么不能将“ref bool”存储在数组中了。如果可以,那么您可以拥有一个比被引用的堆栈变量存活时间更长的数组。我们有两个选择:要么允许这样做,并且如果你弄错了程序会崩溃并可怕地死掉——这是 C 的设计者做出的选择。安全的。我们选择了后者。

    但是让我们更深入地考虑一下。如果你想要传递“允许我设置变量的东西”,我们有 that。那只是一个代表:

    static void DoStuff<T>(this T thing, params Action<T>[] actions)
    {
        foreach(var action in actions) action(thing);
    }
    ...
    bool b = whatever;
    b.DoStuff(x=>{q = x;}, x=>{r = x;} );
    

    有意义吗?

    【讨论】:

    • “设置变量的东西”看起来更像是一个闭包,它将有问题的变量变成了一个类字段。似乎有一种特殊类型的存储位置表示“对持有 n const byrefs 和 m 非 const byrefs 的 blob 的 const ref”,并允许该类型的存储位置仅用于函数参数,应该是安全的。为这样的目的使用闭包似乎很恶心。在我看来,在其范围之外使用变量通常是错误的。与其将变量提升到闭包,不如使用范围有限的方法来传递它们。
    • 我希望看到一个特殊的“ByRefDelegate”,它将静态方法指针与 byref-collection 结合起来;这样的事情不能持久化——只能传递给嵌套的例程。接受委托但不持久化的方法可能具有接受“ByRefDelegates”的重载,并且编译器可以在适用时生成这些重载而不是闭包,从而为常见情况提供很大的性能提升。
    【解决方案2】:

    不幸的是,Java 社区和现在的 .NET,开发人员决定以“安全”为名降低灵活性是首选解决方案,而要以更少的代码行数达到相同的结果,您必须选择非常复杂的(所有这些类结构、委托等)。

    在 Delphi 中,我可以简单地做这样的事情:

    var
      a: integer; f: double; n: integer;
    sscanf(fmtstr, valuestr, [@a, @f, @n]);
    

    //

    在 C# 中你必须这样做:

    int a; double f; int n;
    object [] o = new object[];
    sscanf(fmtstr, valuestr, ref o);
    a = o[0];
    f = o[1];
    n = o[2];
    

    这 5 行代码可以完成我在 1 行 Delphi 代码中可以做的事情。我认为某处有一个公式,代码中出现错误的可能性随着代码行数的增加而呈几何级数增加;所以如果你有 20 行代码,你的代码出现错误的可能性是 10 行代码的 4 倍。

    当然,您可以通过使用带有所有那些奇怪的尖括号和奇怪的语法的委托来减少 # 行代码,但我认为这也是错误的避风港。

    【讨论】:

      【解决方案3】:

      真的没有办法。你可以这样做:

      public static void Main(string[] args)
      {
          BooleanWrapper a = true, b = true, c = true, d = true, e = new BooleanWrapper();
          b.SetTo(a, c, d, e);
      }
      
      public static void SetTo(this BooleanWrapper sourceWrapper, params BooleanWrapper[] wrappers)
      {
          foreach (var w in wrappers)
              w.Value = sourceWrapper.Value;
      }
      
      public class BooleanWrapper
      {
          public BooleanWrapper() { }
      
          public BooleanWrapper(Boolean value)
          {
              Value = value;
          }
      
          public Boolean Value { get; set; }
      
          public static implicit operator BooleanWrapper(Boolean value)
          {
              return new BooleanWrapper(value);
          }
      }
      

      但话说回来,这有什么比这样做更好的呢:

      public static void Main(string[] args)
      {
          Boolean[] bools = new Boolean[5];
          bools.SetTo(bools[1]); // Note I changed the order of arguments. I think this makes more sense.
      }
      
      public static void SetTo(this Boolean[] bools, Boolean value)
      {
          for(int i = 0; i < bools.Length; i++)
              bools[i] = value;
      }
      

      毕竟,数组一系列变量。如果您需要一些行为类似于变量序列的东西,请使用数组。

      【讨论】:

      • 他可以做到这一点,但使用可空版本 - bool?Nullable&lt;bool&gt;
      • 不,Nullables 在传递时会被复制(而不是传递引用),您也不能改变它们的值。
      【解决方案4】:

      这里有一些有趣的解决方案:

      public delegate RecursionRefFunc<T> RecursionRefFunc<T>(ref T arg);
      
      public static RecursionRefFunc<T> Boo<T>(ref T input)
      {
          Console.WriteLine(input); // Work in here
          return Boo;
      }
      
      public static void Main(string[] args)
      {
          int x1 = 1, x2 = 2, x3 = 3, x4 = 4, x5 = 5;
          Boo(ref x1)(ref x2)(ref x3)(ref x4)(ref x5);
      }
      
      // Output: //
      // 1
      // 2
      // 3
      // 4
      // 5
      

      委托可以递归声明。

      在外面返回一个函数,然后再次调用。

      而且你会被代码审查者杀死。


      广告 OW<: href="https://github.com/CWKSC/MyLib_Csharp" rel="nofollow" target="_blank">CWKSC/MyLib_Csharp

      【讨论】:

        【解决方案5】:

        即使bools 是引用类型,这也是不可能的。虽然class 是引用类型,但Boolean[] 中的变量仍然是,只是引用。分配引用的值只会更改该特定变量的值。 ref 变量数组的概念没有意义(因为数组本质上是一系列值)。

        【讨论】:

        • 数组不是一系列值。数组是一系列变量。我们不允许变量引用数组的原因不是这样做不合逻辑;这样做是完全合乎逻辑的。这样做会阻止我们通过移动堆栈指针来优化值类型的局部变量的释放。 就是为什么不能在字段或数组槽中存储对变量的引用。
        • @Eric:显然你是对的;我的意思是说数组是一系列连续分配的变量,因此能够指定任意变量引用列表来构成数组是没有意义的。
        猜你喜欢
        • 1970-01-01
        • 2011-11-13
        • 1970-01-01
        • 2011-01-16
        • 2019-07-25
        • 1970-01-01
        • 2019-04-30
        • 2014-06-21
        • 2021-09-29
        相关资源
        最近更新 更多