Как мне назначить "ссылкой" на поле класса в C#?

Я пытаюсь понять, как назначить "ссылкой" на поле класса в 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" полю теряет ссылку.

Есть ли способ сохранить ссылку при назначении поля?

Благодарю.

4 ответа

Решение

Нет. Ref - это просто соглашение о вызовах. Вы не можете использовать его для квалификации поля. В Z _Example устанавливается значение передаваемой ссылки на строку. Затем вы присваиваете ей новую ссылку на строку, используя +=. Вы никогда не присваиваете пример, поэтому ссылка не имеет никакого эффекта.

Единственный обходной путь для того, что вы хотите - это иметь общий изменяемый объект-обертку (массив или гипотетический 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)";
  }
 }

Как уже отмечали другие, вы не можете иметь поле типа "ref to variable". Однако, просто зная, что вы не можете сделать это, вероятно, неудовлетворительно; Вы, вероятно, также хотите знать, во-первых, почему бы и нет, а во-вторых, как обойти это ограничение.

Причина в том, что есть только три возможности:

1) Запретить поля типа ref

2) Разрешить небезопасные поля типа ref

3) Не используйте пул временного хранилища для локальных переменных (он же "стек")

Предположим, мы допустили поля типа ref. Тогда вы могли бы сделать

public ref int x;
void M()
{
    int y = 123;
    this.x = ref y;
}

и теперь вы можете получить доступ после M завершается. Это означает, что либо мы в случае (2) - доступ this.x упадет и ужасно умрет, потому что хранилище для y больше не существует - или мы в случае (3), и местный y хранится в куче мусора, а не во временном пуле памяти.

Нам нравится оптимизация, заключающаяся в том, что локальные переменные хранятся во временном пуле, даже если они передаются по ссылке, и мы ненавидим идею о том, что вы можете оставить вокруг себя бомбу замедленного действия, которая может привести к аварийному завершению вашей программы и ее смерти позже. Поэтому вариант один это: нет ссылок на поля.

Обратите внимание, что для локальных переменных, которые являются закрытыми переменными анонимных функций, мы выбираем option (3); эти локальные переменные не выделяются из временного пула.

Что приводит нас ко второму вопросу: как вы справляетесь с этим? Если причина, по которой вы хотите использовать поле ref, состоит в том, чтобы сделать метод получения и установки другой переменной, это совершенно законно:

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.
}

И вот, пожалуйста. y хранится в куче gc, и x это объект, который имеет возможность получить и установить y,

Обратите внимание, что CLR поддерживает ref locals и ref return методы, а C# - нет. Возможно, гипотетическая будущая версия C# будет поддерживать эти функции; Я прототипировал его, и он хорошо работает. Тем не менее, это не очень высокий приоритет в списке приоритетов, так что я бы не надеялся.

ОБНОВЛЕНИЕ: функция, упомянутая в предыдущем параграфе, была наконец реализована в C# 7 для реального. Однако вы все еще не можете сохранить ссылку в поле.

Вы забыли обновить ссылку в классе Z:

public class Z {
    private string _Example;

    public Z(ref string example) {
        example = this._Example += " (Updated By Z)";
    }
}

Выход: X (обновляется по Y) (обновляется по Z)

Следует помнить, что оператор += для строки вызывает метод String.Concat(). Который создает новый строковый объект, он не обновляет значение строки. Строковые объекты являются неизменяемыми, у строкового класса нет методов или полей, позволяющих изменить значение. Очень отличается от поведения по умолчанию обычного ссылочного типа.

Поэтому, если вы используете строковый метод или оператор, вы всегда должны возвращать возвращаемое значение переменной. Это довольно естественный синтаксис, типы значений ведут себя одинаково. Ваш код был бы очень похож, если бы вы использовали int вместо строки.

С 2023 года в .NET 8 будут представлены поля ссылок, но только для структур.

Если он вам нужен в классе, правильным способом будет использование StrongBox вместо написания собственного класса-оболочки, как предлагали другие.

Другие вопросы по тегам