繁体   English   中英

如何通过“引用”分配给c#中的类字段?

[英]How do I assign by “reference” to a class field in c#?

我试图了解如何通过“引用”分配给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更新)

正如我所希望的。

似乎为字段分配“参考参数”会丢失参考。

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

谢谢。

正如其他人指出的那样,您不能具有“引用变量”类型的字段。 但是,仅仅知道自己做不到就可能不满意。 您可能还想知道第一,为什么不知道,第二,如何解决此限制。

原因是因为只有三种可能性:

1)禁止引用类型的字段

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

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

假设我们允许使用ref类型的字段。 那你可以做

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

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

我们喜欢将局部变量存储在临时池中(即使它们由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 return方法,尽管C#不支持。 假设的C#未来版本可能会支持这些功能。 我已经制作了原型,并且效果很好。 但是,这在优先级列表上并不是很高,因此我没有希望。

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

编号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)";
  }
 }

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

public class Z {
    private string _Example;

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM