【问题标题】:Setting a ref to a member field in C#在 C# 中将 ref 设置为成员字段
【发布时间】:2011-12-12 18:52:58
【问题描述】:

我想为成员字段分配一个引用。但是我显然不太了解 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

【问题讨论】:

  • 顺便说一句,你得到相同的输出结果是合乎逻辑的(但我知道你正在等待结果 success 被打印)因为 Console.WriteLine 行都在 之后执行 Init() 方法。

标签: c# pass-by-reference


【解决方案1】:

如果您不想引入另一个类,例如 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();
    }

【讨论】:

    【解决方案2】:

    正如其他人所指出的,您不能在 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);
      }
    }
    

    简单易懂。

    【讨论】:

    • 图书馆有MyRef吗?似乎这是一个涉及值类型的常见使用场景。
    【解决方案3】:

    这里确实有两个问题。

    一个,正如其他发帖人所说,你不能严格做你想做的事(你可能可以用 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);
        }
    }
    

    【讨论】:

    • 这个解决方案是这里提出的最优雅的解决方案,谢谢 :-)
    • 信息性问答,但我不同意,以这种方式使用StringBuilder 会破坏其语义目的并且可能更昂贵。这可能是 LOC 方面的最短路径,但与 CLR 的“背道而驰”。 Eric 的简单MyRef&lt;T&gt; 更清晰地传达了目的,并且具有为任何给定类型工作的额外好处。非常好。
    • @MarcL。 - 是的,我同意这一点。我的方法绝对是一种快速而肮脏的方法,主要基于我最初发布时对问题的(负面)反应。
    【解决方案4】:

    正如 JaredPar 所回答的,你不能。

    但问题部分在于字符串是不可变的。将您的参数更改为class Basket { public string status; },您的代码基本上可以工作。不需要ref 关键字,只需更改parameter.status

    另一个选项当然是Console.WriteLine("After: {0}", e.parameter);。将参数包装在(只写)属性中。

    【讨论】:

    • 我通常不喜欢它与字符串的不可变性有关的建议,这在涉及字符串分配的问题中经常出现。重新分配不是突变,不是可变类型。暗示它与不变性有关是一种扭曲,你不同意吗? (尽管将值封装在一个类中的建议是有效的。)
    • 不再是这条老狗了....它只是重新分配因为字符串是不可变的。
    • @Steve,要清楚,您必须使用重新分配因为您不能改变原始字符串。但我更大的观点是赋值就是赋值,类型本身可以是可变的或不可变的,赋值行为x = y; 是相同的。更大的一点是我对问题的阅读是类型本身无关紧要,他想保持对传递的原始变量的引用或别名。为此,所有答案都证实这不是直接可能的,但提供了在类型系统中工作以实现总体目标的方法。
    • (呸,对不起,我是在一个迂腐的肥皂盒上,+1。)
    • 举个例子来说明 Anthony 的观点,考虑一个 List 类型的字段,而不是 string 类型的字段。列表是可变的。尽管如此,obj.StringList = new List&lt;string&gt;(); 是一个赋值,而不是一个突变。
    【解决方案5】:

    在这一行:

    this.parameter = parameter;
    

    ...你复制方法参数到类成员parameter。然后,在Init() 中,您再次将值“成功”分配给 成员parameter。那么,在您的 Console.Writeline 中,您正在写入方法参数的值“失败”,因为您从未真正修改过 方法参数

    你想要做的事情——你想要做的事情——是不可能的,我相信 C#。我不会尝试使用 ref 修饰符传递 string

    【讨论】:

      【解决方案6】:

      听起来您在这里尝试做的是使字段成为对另一个存储位置的引用。基本上有一个ref 字段,就像你有一个ref 参数一样。这在 C# 中是不可能的。

      这样做的一个主要问题是,为了可验证,CLR(和 C#)必须能够证明包含该字段的对象不会比它指向的位置存活得更久。这通常是不可能的,因为对象位于堆上,ref 可以轻松指向堆栈。这两个地方的生命周期语义非常不同(堆通常比堆栈长),因此无法证明之间的ref 是有效的。

      【讨论】:

      • 你可以通过在闭包中捕获原始string s 来做到这一点吗?
      • @asawyer:确实,虽然这只是一种奇特的说法,即“分配一个具有字符串字段的类,然后存储对该类的引用”。如果这就是你想做的,那就去做吧。
      • @Eric 这很有意义。谢谢!
      猜你喜欢
      • 2014-11-27
      • 1970-01-01
      • 1970-01-01
      • 2019-04-09
      • 2011-05-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-04
      相关资源
      最近更新 更多