【问题标题】:How do I assign by "reference" to a class field in C#?如何通过“引用”分配给 c# 中的类字段?
【发布时间】:2011-02-28 03:46:09
【问题描述】:

我试图了解如何通过“引用”分配给 c# 中的类字段。

我有以下示例要考虑:

 public class X
 {

  public X()
  {

   string example = "X";

   new Y( ref example );

   new Z( ref example );

   System.Diagnostics.Debug.WriteLine( example );

  }

 }

 public class Y
 {

  public Y( ref string example )
  {
   example += " (Updated By Y)";
  }

 }

 public class Z
 {

  private string _Example;

  public Z( ref string example )
  {

   this._Example = example;

   this._Example += " (Updated By Z)";

  }

 }

 var x = new X();

运行上述代码时,输​​出为:

X(由 Y 更新)

而不是:

X(由 Y 更新)(由 Z 更新)

如我所愿。

似乎将“ref 参数”分配给字段会丢失引用。

在分配给字段时有什么方法可以保留引用?

谢谢。

【问题讨论】:

    标签: c# reference parameters field


    【解决方案1】:

    没有。 ref 纯粹是一个调用约定。您不能使用它来限定字段。在 Z 中,_Example 被设置为传入的字符串引用的值。然后使用 += 为它分配一个新的字符串引用。您从不分配给示例,因此 ref 无效。

    您想要的唯一解决方法是拥有一个包含引用(此处为字符串)的共享可变包装对象(一个数组或假设的 StringWrapper)。一般来说,如果你需要这个,你可以找到一个更大的可变对象供类共享。

     public class StringWrapper
     {
       public string s;
       public StringWrapper(string s)
       {
         this.s = s;
       }
    
       public string ToString()
       {
         return s;
       }
     }
    
     public class X
     {
      public X()
      {
       StringWrapper example = new StringWrapper("X");
       new Z(example)
       System.Diagnostics.Debug.WriteLine( example );
      }
     }
    
     public class Z
     {
      private StringWrapper _Example;
      public Z( StringWrapper example )
      {
       this._Example = example;
       this._Example.s += " (Updated By Z)";
      }
     }
    

    【讨论】:

    • 您介意扩展第二段吗? “一个更大的可变对象”对我来说并不完全清楚。另外,如果无法在字段中保存字符串引用,StringWrapper 将如何工作?
    • 我给出了一个应该澄清的例子。正如我所说,在实际设计中,StringWrapper 可能是一个业务对象,而不仅仅是一个字符串。
    • 感谢您的想法。我的示例可能过于简化了,因为我的真实代码中确实有一个对象(不是字符串),我试图通过引用进行分配。我正在寻找的行为是允许对象的用户能够将 null 分配给所述对象,甚至可以从其方法中分配该类型的新实例。我通过构造函数将对象作为某些依赖注入的一部分传入,因此其他方法只能通过访问字段来查看对象。我想要的似乎无法实现,这是一种耻辱。
    【解决方案2】:

    您忘记更新 Z 类中的引用:

    public class Z {
        private string _Example;
    
        public Z(ref string example) {
            example = this._Example += " (Updated By Z)";
        }
    }
    

    输出:X(由 Y 更新)(由 Z 更新)

    要记住的一点是,字符串的 += 运算符调用 String.Concat() 方法。它创建了一个 new 字符串对象,它不会更新字符串的值。字符串对象是不可变的,字符串类没有任何方法或字段可以让您更改值。与常规引用类型的默认行为非常不同。

    因此,如果您使用字符串方法或运算符,则始终必须将返回值分配回变量。这是非常自然的语法,值类型的行为方式相同。如果您使用 int 而不是字符串,您的代码将非常相似。

    【讨论】:

    • 感谢汉斯的想法。我的例子有问题——为了简单起见,我在构造函数中进行了更新。实际上,该字段将在构造函数中设置,但在实例化对象后在另一个方法中进行更新。
    【解决方案3】:

    正如其他人所指出的,您不能拥有“引用变量”类型的字段。但是,仅仅知道自己做不到可能并不令人满意;您可能还想首先知道为什么不知道,其次,如何绕过这个限制。

    之所以如此,是因为只有三种可能:

    1) 禁止引用类型的字段

    2) 允许 ref 类型的不安全字段

    3) 不要将临时存储池用于局部变量(也称为“堆栈”)

    假设我们允许 ref 类型的字段。那你就可以了

    public ref int x;
    void M()
    {
        int y = 123;
        this.x = ref y;
    }
    

    现在 y 可以在 M 完成后访问。这意味着要么我们在情况 (2) 中——访问 this.x 将崩溃并可怕地死掉,因为 y 的存储不再存在——或者我们在情况 (3) 中,并且本地 y 是存储在垃圾收集堆上,而不是临时内存池上。

    我们喜欢将局部变量存储在临时池中的优化,即使它们是由 ref 传递的,我们讨厌这样的想法,即您可能会留下一个定时炸弹,这可能会使您的程序崩溃并在以后死掉。因此,选项一是:没有 ref 字段。

    请注意,对于作为匿名函数的封闭变量的局部变量,我们选择选项 (3);这些局部变量不是从临时池中分配出来的。

    这就引出了第二个问题:您如何解决这个问题?如果您想要一个 ref 字段的原因是创建另一个变量的 getter 和 setter,那是完全合法的:

    sealed class Ref<T>
    {
        private readonly Func<T> getter;
        private readonly Action<T> setter;
        public Ref(Func<T> getter, Action<T> setter)
        {
            this.getter = getter;
            this.setter = setter;
        }
        public T Value { get { return getter(); } set { setter(value); } }
    }
    ...
    Ref<int> x;
    void M()
    {
        int y = 123;
        x = new Ref<int>(()=>y, z=>{y=z;});
        x.Value = 456;
        Console.WriteLine(y); // 456 -- setting x.Value changes y.
    }
    

    然后就可以了。 y存储在gc堆上,x是一个可以获取和设置y的对象。

    请注意,CLR 确实支持 ref locals 和 ref 返回方法,但 C# 不支持。也许假设的 C# 未来版本将支持这些特性;我已经对其进行了原型设计,并且效果很好。但是,这在优先级列表中并不高,所以我不会抱太大希望。

    更新:上面段落中提到的功能最终在 C# 7 中实现。但是,您仍然不能在字段中存储 ref。

    【讨论】:

    • 为什么需要 getter 和 setter - 闭包对于让 y 保持装箱很重要?是否可以简单地使用 Ref { public T Value { get;放; } } 而不是?
    • @ChrisMoschini:当然,这完全有可能。但是你传递的是对一个值的引用,而不是一个变量。问题的重点是“如何存储对变量的引用?”假设您想要存储对数组第 12 个元素的“引用”而不是局部变量 y,因此改变引用会改变数组的第 12 个元素。您建议的类型在这种情况下没有帮助。
    • 是的,虽然两者都不会传递 .someMethod(ref array[12]),这似乎更符合问题的精神。不幸的是,我们的建议都没有为您提供一个装箱并保持装箱的值类型。 Nullable 实际上可能是与提问者尝试的最接近的匹配项。
    • 这很简单。谢谢埃里克!
    • 类不能有 ref 类型的字段并不意味着结构不能安全地这样做,尽管人们可以用这样的结构做的一系列事情将被限制在其最严格的范围内成员。如果将 byref 细分为可返回的和不可返回的,这样的结构将有一个重要的用例。函数只能返回可返回的 byrefs,而局部变量只能作为不可返回的传递。将不可返回的 byref 作为可返回的 byref 参数传递将导致该调用的返回值不可返回。
    猜你喜欢
    • 1970-01-01
    • 2012-05-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-20
    • 1970-01-01
    • 1970-01-01
    • 2013-07-28
    相关资源
    最近更新 更多