Как назначить "ссылку" на поле класса в С#?

Я пытаюсь понять, как назначить "ссылку" на поле класса в С#.

У меня есть следующий пример:

 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 parameter" полю теряет ссылку.

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

Спасибо.

Ответ 1

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

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

Ответ 2

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

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

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

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

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

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

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

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

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

Обратите внимание, что для локальных переменных, которые являются замкнутыми переменными анонимных функций, мы выбираем опцию (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 и возвращает возвращаемые методы, хотя С# этого не делает. Возможно, гипотетическая будущая версия С# будет поддерживать эти функции; Я прототипировал его, и он работает хорошо. Однако это не очень важно в списке приоритетов, поэтому я не буду надеяться.

UPDATE: функция, упомянутая в предыдущем абзаце, была наконец реализована для реального в С# 7. Однако вы все еще не можете сохранить ref в поле.

Ответ 3

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

public class Z {
    private string _Example;

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

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

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

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