【问题标题】:c#: keep ref parameter from constructor in classc#:在类中保留构造函数的 ref 参数
【发布时间】:2022-04-22 08:10:04
【问题描述】:

基本上我希望能够在一个类的实例中引用一个变量,但我希望该引用成为一个类变量,所以我不需要将它作为参数发送到类内部

代码:

int num = 0;
myClass(num);
print num; // output is 0 but i'd like it to be 10 :)
class myClass
{
    private int classNumber;
    myClass(ref int number)
    {
        print number; //output is 0

        // id like this to be a reference to the refenrence
        classNumber = number;

        DoSomething();
    }
    public void DoSomething()
    {
        ClassNumber = 10;
    }
}

为什么我问这个是因为我正在使用 winforms 并且有一个主表单将一个类的实例发送到一个应该编辑类并将其发送回的新表单..现在我使用 Form.ShowDialog () 避免用户在新表单中编辑时使用主表单,然后从新表单中获取数据

editForm edtfrm = new editForm(ref instanceOfClass);
edtfrm.showDialog();
//grab the instance back
instanceOfClass = edtfrm.editedClass;

我该如何解决这个问题?我不喜欢这个解决方案

【问题讨论】:

  • 您的两个问题似乎完全无关。您确定问题 1 的解决方案将帮助您解决问题 2?
  • "problem 2" 就是我如何解决这个难题 atm .. 通过创建我引用的内容的副本,然后当我完成编辑它时将其复制回来,如果我这样做了,则在问题 1 中“ classNumber" public 并且在打印之前我会做num = myClass.classNumber
  • 由于instanceOfClass是一个引用,你不需要使用ref来传递它(因为你想改变instanceOfClass内容,而不是指针本身)。我也不明白为什么你必须“抢回实例”...... instanceOfClass 和editedClass 应该仍然都指向内存中的同一个对象,不是吗?

标签: c# winforms reference


【解决方案1】:

我希望能够在一个类的实例中引用一个变量,但我希望该引用成为一个类变量,所以我不需要将它作为参数发送到类内部

那时你将不得不忍受失望。 CLR 类型系统明确禁止将对变量的引用存储为类的成员。 CLR 允许对变量的引用是

  • 作为与形参或“this”相对应的参数传递给方法
  • 存储为本地人
  • 作为方法返回值返回

不允许 允许存储在数组、字段等中。基本上,任何“在堆上”的东西都不能保留引用。

C# 公开了第一个特性:引用变量作为方法参数。它没有暴露其他两个特性(尽管我已经编写了一个 C# 的实验版本,它确实工作得很好。)

请注意,C# 不允许您在需要堆存储 ref 的上下文中使用 ref——例如,ref 参数是 lambda 的封闭外部变量。在极少数情况下,编译器确实允许看起来像长期存储 ref,并使用 copy-in-copy-out 语义来模拟 ref,但最好不要去那里。

为什么 CLR 有这个限制?正确的思考方式是有两种存储:长期和短期,通常称为“堆”和“栈”。但是数据结构的形状是无关紧要的;相关的是生命的长度。一个变量有一个存储位置;这就是变量。如果您可以在长期存储中保留从短期存储分配的变量的引用,那么长期存储会保留对生命周期较短的东西的引用,因此在访问变量时可能会崩溃和死亡在它死后。

显然有很多方法可以解决这个问题。例如,CLR 团队可以选择将 ref 短期存储为非法,并允许将 ref 存储在长期存储中。但这意味着你不能对局部变量或参数进行引用,因为它们的寿命很短,所以你想把它们放在短期存储中。

CLR 团队实际选择的方式是禁止长期存储任何 ref。与任何设计决策一样,它是与相互竞争的目标进行多次权衡的结果。

【讨论】:

    【解决方案2】:

    您要做什么并不是一个好主意,我会将修改后的对象公开为类的属性,如下所示:

    public class ClassContructorReference
    {
        static void Main(string[] args)
        {
            object var = new object();
            MyClass myClass = new MyClass(var);
            StringBuilder mySb = myClass.Instance as StringBuilder;
            Console.WriteLine(mySb.ToString());
        }
    }
    
    public class MyClass
    {
        public object Instance {get;set;}
    
        public MyClass(object var)
        {
            this.Instance = var;
            DoSomething();
        }
    
        private void DoSomething()
        {
            this.Instance = new StringBuilder("Hello");
        }
    }
    

    【讨论】:

      【解决方案3】:

      当然,您的测试代码将无法工作,因为它是原始类型。但是您的第二个代码将工作,因为它是引用类型。(甚至不需要'ref')无需重新分配实例。

      public class Second
      {
          public First f;
      
          public Second(First f)
          {
              this.f= f;
          }
      
          public void change()
          {
              this.f.Name = "PUli";
          }
      }
      public class First
      {
          private string _name;
      
          public First()
          {
              Name = "SUli";
          }
      
          public string Name
          {
              get { return _name; }
              set { _name = value; }
          }
      }
      class Program
      {
          static void Main(String[] args)
          {
              First f = new First();
              Second sec = new Second(f);
              Console.WriteLine(f.Name);
              sec.change();
              Console.WriteLine(f.Name);
          }
      }
      

      输出:-

      苏力

      普利

      【讨论】:

        【解决方案4】:

        创建一个包含您的号码作为属性的类,并将其传递给您的逻辑。该类将代表您的“模型”。

        【讨论】:

          【解决方案5】:

          您不能保存ref 参数,ref 不是参考,它只是一个alias。如果你有:

          public void Stuff (ref int i)
          {
            i = 2;
          }
          

          然后调用它:

          int s = 1;
          Stuff(ref s);
          

          ref 的意思是“使 i 成为 s 的别名并将更改传播给它”。一旦离开方法的范围,该别名就消失了。顺便说一句,Eric Lippert 在他的blog 上开始了一个关于这个的系列。

          您应该创建一个类并在您的逻辑中使用它。 GUI 不应该操纵值,只有后端应该。

          【讨论】:

            【解决方案6】:

            这里有几件事。 首先,在你的构造函数中你可能想要做

            DoSomething();     
            number=classnumber;
            

            而不是

            classnumber=number;
            

            其次,试试

            myClass(ref num);
            

            而不是

            myClass(num);
            

            【讨论】:

            • 我在帖子中添加了ref ;)。这可能适用于我提出问题的方式。但是我的问题在于winforms,因此我不能像我一样在构造函数中进行所有编辑。因为新表单在另一个线程中,我需要在整个新表单中保持该引用有效
            【解决方案7】:

            这是一个非常古老的问题,但我认为你可以通过一个涉及捕获的小技巧来做你想做的事情。

            例如:

            int meh = 9;
            
            MyClass myClass = new(() => meh, o => meh = o);
            
            meh = 768;
            
            Console.WriteLine(myClass.CapturedInt); // Displays 768
            
            myClass.CapturedInt = 1024;
            
            Console.WriteLine(meh); // Displays 1024
            
            class MyClass
            {
                private readonly Func<int> readMethod;
                private readonly Action<int> writeMethod;
            
                public int CapturedInt
                {
                    get => readMethod();
                    set => writeMethod(value);
                }
            
                public MyClass(Func<int> read, Action<int> write)
                {
                    readMethod = read;
                    writeMethod = write;
                }
            }
            

            这里更接近你的例子:

            int num = 0;
            
            MyClass myClass = new(() => num, o => num = o);
            
            Console.WriteLine(num); // Displays 10
            
            class MyClass
            {
                private readonly Func<int> readMethod;
                private readonly Action<int> writeMethod;
            
                public MyClass(Func<int> read, Action<int> write)
                {
                    readMethod = read;
                    writeMethod = write;
            
                    DoSomething();
                }
            
                public void DoSomething()
                {
                    writeMethod(10);
                }
            }
            

            【讨论】:

              猜你喜欢
              • 2011-09-25
              • 1970-01-01
              • 2019-09-27
              • 1970-01-01
              • 2017-07-20
              • 2011-05-30
              • 2013-11-05
              • 1970-01-01
              • 2014-12-15
              相关资源
              最近更新 更多