在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#中为成员字段设置一个ref

正如其他人指出的那样,在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是有效的。

如果你不想引入另一个类,如MyRefStringBuilder,因为你的字符串已经是一个现有类的属性,你可以使用FuncAction来实现你正在寻找的结果。

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);。将参数包装在(只写)属性中。