我试图了解如何通过“引用”分配给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参数”会丢失引用。
分配到字段时是否有任何方法可以保留引用?
感谢。
答案 0 :(得分:64)
正如其他人所说,你不能有“ref to variable”类型的字段。然而,只知道你不能做到这一点可能并不令人满意;你可能也想先知道,为什么不知道,其次,如何解决这个限制。
原因是因为只有三种可能性:
1)禁止ref类型的字段
2)允许ref类型的不安全字段
3)不要将临时存储池用于局部变量(也称为“堆栈”)
假设我们允许ref类型的字段。然后你可以做
public ref int x;
void M()
{
int y = 123;
this.x = ref y;
}
现在可以在M
完成后访问y。这意味着我们要么遇到(2) - 访问this.x
会崩溃并死亡,因为y的存储不再存在 - 或者我们遇到(3),而本地{{ 1}}存储在垃圾收集堆上,而不是临时内存池中。
我们喜欢局部变量存储在临时池中的优化,即使它们是由ref传递的,我们也不喜欢你可以留下定时炸弹,这可能会导致程序崩溃并在以后死亡。因此,选项一是:没有参考字段。
请注意,对于作为匿名函数的封闭变量的局部变量,我们选择option(3);这些局部变量不会从临时池中分配出来。
然后我们回到第二个问题:你如何解决它?如果你想要一个ref字段的原因是做另一个变量的getter和setter,那就完全合法了:
y
你去吧。 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.
}
存储在gc堆上,y
是一个能够获取和设置x
的对象。
请注意,CLR确实支持ref本地和ref返回方法,但C#不支持。也许C#的假设未来版本将支持这些功能;我有它的原型,它运作良好。但是,这在优先级列表中并不是很高,所以我不会抱有希望。
更新:上面段落中提到的功能最终在C#7中实现了。但是,您仍然无法在某个字段中存储引用。
答案 1 :(得分:9)
没有。 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)";
}
}
答案 2 :(得分:3)
您忘记更新Z类中的引用:
public class Z {
private string _Example;
public Z(ref string example) {
example = this._Example += " (Updated By Z)";
}
}
输出:X(由Y更新)(由Z更新)
要记住的是,字符串的+ =运算符调用String.Concat()方法。这会创建一个 new 字符串对象,但不会更新字符串的值。 String对象是不可变的,字符串类没有任何允许您更改值的方法或字段。与常规引用类型的默认行为非常不同。
因此,如果使用字符串方法或运算符,则必须始终将返回值赋给变量。这是非常自然的语法,值类型的行为方式相同。如果您使用int而不是字符串,那么您的代码将非常相似。