В c # при отправке параметра в метод, когда мы должны использовать «ref», а когда «out» и когда без них? - PullRequest
4 голосов
/ 12 июля 2009

В c # при отправке параметра в метод, когда мы должны использовать "ref", а когда "out" и когда без них?

Ответы [ 7 ]

13 голосов
/ 12 июля 2009

В общем, вы должны избегать использования ref и out, если это возможно.

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

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

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

Также помните, что C # передает ссылочные типы (классы) по ссылке (например, ссылка передается по значению). Таким образом, если вы предоставляете какой-либо метод со ссылочным типом в качестве параметра, метод может изменить данные объекта; даже без реф или вне. Но он не может изменить саму ссылку (например, он не может изменить объект, на который ссылается).

12 голосов
/ 12 июля 2009

Они используются в основном для получения нескольких возвращаемых значений из вызова метода. Лично я склонен их не использовать. Если я хочу получить несколько возвращаемых значений из метода, я создам небольшой класс для их хранения.

ref и out используются, когда вы хотите что-то вернуть из метода в этом параметре. Насколько я помню, они оба на самом деле компилируются в один и тот же IL, но C # добавляет некоторые дополнительные вещи, так что вы должны быть конкретны.

Вот несколько примеров:

static void Main(string[] args)
{
    string myString;
    MyMethod0(myString);
    Console.WriteLine(myString);

    Console.ReadLine();
}

public static void MyMethod0(string param1)
{
    param1 = "Hello";
}

Выше не скомпилируется, потому что myString никогда не инициализируется. Если myString инициализируется в string.Empty, то результатом программы будет пустая строка, потому что все, что делает MyMethod0, - это назначает новую строку локальной ссылке на param1.

static void Main(string[] args)
{
    string myString;
    MyMethod1(out myString);
    Console.WriteLine(myString);

    Console.ReadLine();
}


public static void MyMethod1(out string param1)
{
    param1 = "Hello";
}

myString не инициализируется в методе Main, но программа выводит «Hello». Это связано с тем, что ссылка myString в методе Main обновляется из MyMethod1. MyMethod1 не ожидает, что param1 уже содержит что-либо, поэтому его можно оставить неинициализированным. Однако метод должен назначать что-либо.

static void Main(string[] args)
{
    string myString;
    MyMethod2(ref myString);
    Console.WriteLine(myString);

    Console.ReadLine();
}

public static void MyMethod2(ref string param1)
{
    param1 = "Hello";
}

Это опять не скомпилируется. Это потому, что ref требует, чтобы myString в методе Main сначала инициализировался чем-то. Но, если метод Main изменен так, что myString инициализируется в string.Empty, тогда код скомпилируется, и на выходе будет Hello.

Таким образом, разница может быть использована с неинициализированным объектом, ref должен быть передан инициализированному объекту. И если вы передаете объект без ссылки на него, его нельзя заменить.

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

static void Main(string[] args)
{
    string myString = "Hello";
    MyMethod0(myString);
    Console.WriteLine(myString);

    Console.ReadLine();
}

public static void MyMethod0(string param1)
{
    param1 = "World";
}

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

Надеюсь, это имеет смысл. Мое общее правило - просто не использовать их. Я чувствую, что это возвращение к предыдущим дням. (Но это только мое мнение)

6 голосов
/ 12 июля 2009

(это дополняет существующие ответы - несколько дополнительных соображений)

Существует другой сценарий использования ref с C #, более часто встречающийся в таких вещах, как XNA ... Обычно, когда вы передаете тип значения (struct), он клонируется. Это использует пространство стека и несколько циклов ЦП, и имеет побочный эффект, что любые изменения struct в вызванном методе будут потеряны.

(кроме: обычно struct s должны быть неизменяемыми, но изменяемые структуры не редкость в XNA)

Чтобы обойти это, довольно часто можно увидеть ref в таких программах.

Но в большинстве программ (т. Е. Там, где вы используете class es по умолчанию), вы можете обычно просто передать ссылку "по значению" (т.е. без ref / out). * * тысяча двадцать-одна


Другой очень общий вариант использования out - это шаблон Try*, например:

string s = Console.ReadLine();
int i;
if(int.TryParse(s, out i)) {
    Console.WriteLine("You entered a valid int: " + i);
}

или аналогично TryGetValue в словаре.

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

2 голосов
/ 31 августа 2009

В дополнение к подробному ответу Колина вы также можете использовать параметры для возврата нескольких значений из одного вызова метода. См., Например, метод ниже, который возвращает 3 значения.

static void AssignSomeValues(out int first, out bool second, out string third)
    {
        first = 12 + 12;
        second = false;
        third = "Output parameters are okay";
    }

Вы можете использовать это так

static void Main(string[] args) {
        int i;
        string s;
        bool b;

        AssignSomeValues(out i, out b, out s);

        Console.WriteLine("Int value: {0}", i);
        Console.WriteLine("Bool value: {0}", b);
        Console.WriteLine("String value: {0}", s);

        //wait for enter key to terminate program
        Console.ReadLine(); }

Просто убедитесь, что вы присвоили правильное значение каждому параметру out, чтобы избежать ошибки.

2 голосов
/ 12 июля 2009

Очень просто на самом деле. Вы используете точно такое же ключевое слово, с которым параметр был первоначально объявлен в методе. Если он был объявлен как out, вы должны использовать out. Если он был объявлен как ref, вы должны использовать ref.

0 голосов
/ 21 июля 2009

ref следует избегать (я полагаю, что для этого также есть правило fx-cop), однако используйте ref, когда объект, на который ссылается, может сам измениться Если вы видите ключевое слово ref, вы знаете, что на базовый объект больше нельзя ссылаться из той же переменной после вызова метода.

0 голосов
/ 13 июля 2009

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

Всякий раз, когда я вижу реф, я нервничаю.

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