Как мне назначить "ссылкой" на поле класса в C #? - PullRequest
27 голосов
/ 05 июня 2010

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

Как я и надеялся.

Кажется, что присвоение "параметра ref" полю теряет ссылку.

Есть ли способ сохранить ссылку при присвоении полю?

Спасибо.

Ответы [ 3 ]

66 голосов
/ 06 июня 2010

Как уже отмечали другие, вы не можете иметь поле типа "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 хранится в куче собранного мусора, а не во временном пуле памяти.

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

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

9 голосов
/ 05 июня 2010

Нет. 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)";
  }
 }
3 голосов
/ 05 июня 2010

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

public class Z {
    private string _Example;

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

Вывод: X (обновлено по Y) (обновлено по Z) * ​​1004 *

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...