在c#中为成员字段设置一个ref
本文关键字:一个 ref 设置 成员 字段 | 更新日期: 2023-09-27 18:12:05
我想给一个成员字段分配一个引用。但我显然不理解c#的这一部分很好,因为我失败了:-)所以,这是我的代码:
public class End {
public string parameter;
public End(ref string parameter) {
this.parameter = parameter;
this.Init();
Console.WriteLine("Inside: {0}", parameter);
}
public void Init() {
this.parameter = "success";
}
}
class MainClass {
public static void Main(string[] args) {
string s = "failed";
End e = new End(ref s);
Console.WriteLine("After: {0}", s);
}
}
输出是:
Inside: failed
After: failed
我如何在控制台上获得"成功"?
提前感谢,dijxtra
正如其他人指出的那样,在c#或任何CLR语言中,不能将对变量的引用存储在字段中。
当然,你可以很容易地捕获对包含变量的类实例的引用:sealed class MyRef<T>
{
public T Value { get; set; }
}
public class End
{
public MyRef<string> parameter;
public End(MyRef<string> parameter)
{
this.parameter = parameter;
this.Init();
Console.WriteLine("Inside: {0}", parameter.Value);
}
public void Init()
{
this.parameter.Value = "success";
}
}
class MainClass
{
public static void Main()
{
MyRef<string> s = new MyRef<string>();
s.Value = "failed";
End e = new End(s);
Console.WriteLine("After: {0}", s.Value);
}
}
容易peasy。
这里有两个问题。
第一,正如其他海报所说,你不能严格地做你想做的(因为你可以用C和类似的)。然而,在c#中,行为和意图仍然是可行的——你只需要用c#的方式来做。另一个问题是你试图使用字符串的不幸尝试-正如其他海报提到的-不可变-并且根据定义可以被复制。
所以,话虽如此,你的代码可以很容易地转换成这样,我认为这是你想要的:public class End
{
public StringBuilder parameter;
public End(StringBuilder parameter)
{
this.parameter = parameter;
this.Init();
Console.WriteLine("Inside: {0}", parameter);
}
public void Init()
{
this.parameter.Clear();
this.parameter.Append("success");
}
}
class MainClass
{
public static void Main(string[] args)
{
StringBuilder s = new StringBuilder("failed");
End e = new End(s);
Console.WriteLine("After: {0}", s);
}
}
听起来您在这里要做的是使字段成为对另一个存储位置的引用。本质上有一个ref
域就像你有一个ref
参数一样。这在c#中是不可能的。
这样做的一个主要问题是,为了能够验证CLR(和c#)必须能够证明包含该字段的对象不会比它指向的位置更长。这通常是不可能的,因为对象位于堆上,ref
可以很容易地指向堆栈。这两个位置具有非常不同的生命周期语义(堆通常比堆栈长),因此不能证明两者之间的ref
是有效的。
如果你不想引入另一个类,如MyRef
或StringBuilder
,因为你的字符串已经是一个现有类的属性,你可以使用Func
和Action
来实现你正在寻找的结果。
public class End {
private readonly Func<string> getter;
private readonly Action<string> setter;
public End(Func<string> getter, Action<string> setter) {
this.getter = getter;
this.setter = setter;
this.Init();
Console.WriteLine("Inside: {0}", getter());
}
public void Init() {
setter("success");
}
}
class MainClass
{
public static void Main(string[] args)
{
string s = "failed";
End e = new End(() => s, (x) => {s = x; });
Console.WriteLine("After: {0}", s);
}
}
如果你想进一步简化调用端(以牺牲一些运行时间为代价),你可以使用像下面这样的方法将(一些)getter转换为setter。
/// <summary>
/// Convert a lambda expression for a getter into a setter
/// </summary>
public static Action<T, U> GetSetter<T,U>(Expression<Func<T, U>> expression)
{
var memberExpression = (MemberExpression)expression.Body;
var property = (PropertyInfo)memberExpression.Member;
var setMethod = property.GetSetMethod();
var parameterT = Expression.Parameter(typeof(T), "x");
var parameterU = Expression.Parameter(typeof(U), "y");
var newExpression =
Expression.Lambda<Action<T, U>>(
Expression.Call(parameterT, setMethod, parameterU),
parameterT,
parameterU
);
return newExpression.Compile();
}
这一行:
this.parameter = parameter;
…您将方法参数复制到类成员parameter
。然后,在Init()
中,您将值"success"再次分配给类成员parameter
。在你的控制台。Writeline,那么,您正在写入方法参数的值,"failed",因为您从未实际修改方法参数。
你想做的事情——你想做的方式——是不可能的,我相信c#。我不会尝试用ref
修饰符传递string
。
正如JaredPar的回答,你不能。
但是问题部分在于string是不可变的。将参数更改为class Basket { public string status; }
,您的代码将基本工作。不需要ref
关键字,只需更改parameter.status
。
另一个选项当然是Console.WriteLine("After: {0}", e.parameter);
。将参数包装在(只写)属性中。