Подтвердить что ты не робот

Использование ключевого слова С# ref

Я понимаю (или, по крайней мере, я полагаю, что знаю), что значит передать экземпляр класса методу ref по сравнению с тем, что он не проходит через ref. Когда или при каких обстоятельствах следует передавать экземпляр класса с помощью ref? Есть ли наилучшая практика, когда дело доходит до использования ключевого слова ref для экземпляров класса?

4b9b3361

Ответ 1

Самым ясным объяснением, которое я когда-либо встречал для параметров вывода и ссылки, было... Джон Скит.

Передача параметра в С#

Он не использует "лучшие практики", но если вы поймете приведенные им примеры, вы поймете, когда вам нужно их использовать.

Ответ 2

Если вы можете заменить исходный объект, вы должны отправить его как ref. Если он только для вывода и может быть неинициализирован до вызова функции, вы будете использовать out.

Ответ 3

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

Это не то же самое, что передавать ссылочный тип в качестве параметра функции. В этих случаях вы все еще передаете значение, но значение является ссылкой. В случае прохождения через ref, тогда отправляется фактическая ссылка на переменную; по сути, вы и функция, которую вы вызываете "совместно" с той же переменной.

Рассмотрим следующее:

public void Foo(ref int bar)
{
    bar = 5;
}

...

int baz = 2;

Foo(ref baz);

В этом случае переменная baz имеет значение 5, так как она была передана по ссылке. Семантика полностью понятна для типов значений, но не так понятна для ссылочных типов.

public class MyClass
{
    public int PropName { get; set; }
}

public void Foo(MyClass bar)
{
    bar.PropName = 5;
}

...

MyClass baz = new MyClass();

baz.PropName = 2;

Foo(baz);

Как и ожидалось, baz.PropName будет 5, так как MyClass является ссылочным типом. Но позвольте сделать это:

public void Foo(MyClass bar)
{
    bar = new MyClass();

    bar.PropName = 5;
}

С тем же кодом вызова baz.PropName останется 2. Это связано с тем, что хотя MyClass является ссылочным типом, Foo имеет свою собственную переменную для bar; bar и baz начинаются с одного значения, но однажды Foo присваивает новое значение, это всего лишь две разные переменные. Если, однако, мы делаем это:

public void Foo(ref MyClass bar)
{
    bar = new MyClass();

    bar.PropName = 5;
}

...

MyClass baz = new MyClass();

baz.PropName = 2;

Foo(ref baz);

Мы закончим с PropName равным 5, поскольку мы передали baz по ссылке, что делает две функции "совместно" одной и той же переменной.

Ответ 4

Ключевое слово ref позволяет передавать аргумент по ссылке. Для ссылочных типов это означает, что фактическая ссылка на объект передается (а не копия этой ссылки). Для типов значений это означает, что передается ссылка на переменную, содержащую значение этого типа.

Это используется для методов, которые должны возвращать более одного результата, но не возвращают сложный тип для инкапсуляции этих результатов. Он позволяет передавать ссылку на объект в метод, чтобы метод мог модифицировать этот объект.

Важно помнить, что ссылочные типы обычно не передаются по ссылке, копия ссылки передается. Это означает, что вы не работаете с фактической ссылкой, которая была передана вам. Когда вы используете ref в экземпляре класса, вы передаете фактическую ссылку, поэтому все изменения к ней (например, установка ее на null, например) будут применены к исходной ссылке.

Ответ 5

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

Например:

public void MyMethod(ref MyClass obj) 
{
   obj = new MyClass();
}

в других местах:

MyClass x = y; // y is an instance of MyClass

// x points to y

MyMethod(ref x);

// x points to a new instance of MyClass

при вызове MyMethod (ref x), x будет указывать на вновь созданный объект после вызова метода. x больше не указывает на исходный объект.

Ответ 6

Большинство вариантов использования ссылочной переменной по ссылке включают инициализацию, а out более подходит, чем ref. И они скомпилируются в одно и то же (компилятор применяет различные ограничения), чтобы переменные ref инициализировались до того, как их передали, и эти переменные инициализируются в методе). Таким образом, единственный случай, когда я могу думать о том, где это было бы полезно, - это то, где вам нужно выполнить некоторую проверку экземпляра константы ref и, возможно, потребуется повторная инициализация при определенных обстоятельствах.

Это также может потребоваться для изменения неизменяемого класса (например, строки), как указано Asaf R.

Ответ 7

Я обнаружил, что с помощью ключевого слова ref легко столкнуться с трудностями.

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

public void TrySet(Foo f,string s)
{
   f.Bar = s;
}

В этом втором случае исходный Foo затрагивается только первой строкой кода, остальная часть метода каким-то образом создает и влияет только на новую локальную переменную.

public void TryNew(Foo f, string s)
{
  f.Bar = ""; //original f is modified
  f = new Foo(); //new f is created
  f.Bar = s; //new f is modified, no effect on original f
}

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

Фактически вы хотите заменить объект новым экземпляром, используйте ключевое слово ref:

public void TryNew(ref Foo f, string s)...

Но разве ты не стреляешь в ногу? Если вызывающий абонент не знает, что создается новый объект, следующий код, вероятно, не будет работать так, как предполагалось:

Foo f = SomeClass.AFoo;
TryNew(ref f, "some string"); //this will clear SomeClass.AFoo.Bar and then create a new distinct object

И если вы попытаетесь "исправить" проблему, добавив строку:

SomeClass.AFoo = f;

Если код содержит ссылки на SomeClass.AFoo где-то еще, эта ссылка станет недействительной...

Как правило, вам, вероятно, следует избегать использования нового ключевого слова для изменения объекта, который вы читаете из другого класса или полученного как параметр в методе.

Что касается использования ключевого слова ref с ссылочными типами, я могу предложить этот подход:

1) Не используйте его, если просто задайте значения ссылочного типа, но явны в ваших именах функций или параметров и в комментариях:

public void SetFoo(Foo fooToSet, string s)
{
  fooToSet.Bar = s;
}

2) Когда есть законная причина заменить входной параметр новым, другим экземпляром, вместо этого используйте функцию с возвращаемым значением:

public Foo TryNew(string s)
{
  Foo f = new Foo();
  f.Bar = s;
  return f;
}

Но использование этой функции может по-прежнему иметь нежелательные последствия для сценария SomeClass.AFoo:

SomeClass.AFoo = TryNew("some string");//stores a different object in SomeClass.AFoo 

3) В некоторых случаях, таких как пример замены строк здесь, полезно использовать ref params, но так же, как в случае 2, убедитесь, что что замена адресов объектов не влияет на остальную часть вашего кода.

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

Наконец, это обычно так, когда нужно использовать функцию memcpy, но в С# нет такой вещи, о которой я знаю.