Когда использование ключевого слова C# ref является хорошей идеей?

Чем больше я вижу, что ref используется в производственном коде, тем чаще я сталкиваюсь с неправильным использованием и тем больше боли это причиняет мне. Я возненавидел это ключевое слово, потому что с точки зрения создания фреймворка оно кажется глупым. Когда было бы неплохо сообщить пользователям вашего кода о том, что может быть изменена ссылка/значение объекта из-под них?

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

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


person bwerks    schedule 21.08.2010    source источник


Ответы (10)


В Руководстве по проектированию фреймворка (книга Кшиштофа Квалины и Брэда Абрамса) рекомендуется избегать параметров ref и out.

ИЗБЕГАЙТЕ использование параметров out или ref.

Использование параметров out или ref требует опыта работы с указателями, понимания различий между типами значений и ссылочными типами и обработки методов с несколькими возвращаемыми значениями. Кроме того, разница между параметрами out и ref не совсем понятна. Архитекторы фреймворков, проектирующие для широкой аудитории, не должны ожидать, что пользователи освоят работу с out или ref параметрами.

В Руководстве по проектированию фреймворка канонический метод Swap упоминается как допустимое исключение:

void Swap<T>(ref T obj1, ref T obj2)
{
    T temp = obj1;
    obj1 = obj2;
    obj2 = temp;
}

но в то же время комментарий отмечает

Swap всегда упоминается в этих дискуссиях, но я не писал код, который действительно нуждался бы в методе swap со времен колледжа. Если у вас нет веской причины, вообще избегайте out и ref.

person dtb    schedule 21.08.2010
comment
Я предполагаю, что Квалина и Абрамс не консультировались, когда разрабатывались TryParse методы. :) - person David Hoerster; 22.08.2010
comment
то же самое можно сказать и о методе IDictionary‹TKey, TValue›.TryGetValue. Это не так убедительно, если .NET framework не последует их предложению. - person treehouse; 22.08.2010
comment
@D Hoerster - на самом деле в книге шаблон TryParse обсуждается в положительном свете. Это рекомендация избегать, а это означает, что известны случаи, когда нарушение правила имеет смысл. - person TrueWill; 22.08.2010
comment
Конечно, для тех из нас, кто разбирается в указателях, ref является бесценным дополнением для повышения эффективности в некоторых обстоятельствах — TryParse и Swap — отличные примеры. Правило действительно должно учитывать другие конструкции, а не слепо использовать ref везде, особенно если вы действительно не понимаете, что означает ref. - person Jason Williams; 23.08.2010
comment
TryParse берет параметр out, а не ref — в этом весь смысл. - person bwerks; 24.08.2010
comment
Ну, если вы разрабатываете универсальное программирование, вам это может понадобиться. Пример: если вы пишете Foo‹T› (T input) { T = this.x; } поведение будет отличаться, если вы вызовете Foo‹int› и Foo‹MyClass› , с ref у вас будет тот же результат. - person kappa; 20.09.2012
comment
Это ужасное предположение о всеобщем отупении среди инженеров. - person user1725145; 06.11.2013

Большинство Interlocked методов используют ref параметров для (I уверена, что вы согласны) веская причина.

person Timwi    schedule 21.08.2010

Я стараюсь избегать этого в общедоступных API, но у него определенно есть применение. Изменяемые типы значений важны, особенно в таких вещах, как CF (где изменяемые структуры более распространены из-за требований платформы). Однако, возможно, чаще всего я использую его при рефакторинге частей сложного алгоритма в несколько методов, где объект состояния является излишним, и мне нужно передать несколько значений:

i.e.

var x = .....
var y = .....
// some local code...
var z = DoSomethingSpecific(ref x, ref y); // needs and updates x/y
// more local code...

и т. д. Где DoSomethingSpecific - это частный метод, который просто перемещен, чтобы сохранить ответственность за метод управляемой.

person Marc Gravell    schedule 21.08.2010

Каждый раз, когда вы хотите изменить значение типа value — это часто происходит в случаях, когда вы хотите эффективно обновить пару связанных значений (т. е. вместо того, чтобы возвращать структуру, содержащую два целых числа, вы передаете (ссылка int x, ссылка int y))

person Jason Williams    schedule 21.08.2010
comment
в чем преимущество использования ref по сравнению с out? - person silvo; 22.08.2010
comment
out означает, что объект должен быть инициализирован первым, с ref это не обязательно. - person Malfist; 22.08.2010
comment
@Malfist Я думаю, у тебя это наоборот. - person siride; 22.08.2010
comment
Мой ответ на это заключается в том, что в этом случае просто используйте out. Если вы хотите изменений, объявите новую переменную и передайте ее, возможно, позже присвоив возвращаемое значение тому, которое у вас уже есть. Другие, кто попытается прочитать ваш код позже, будут восхвалять вас на протяжении поколений. - person bwerks; 22.08.2010
comment
Out можно использовать только для инициализации переменных. ref необходимо использовать, если вы хотите обновлять переменные во время их существования. - person Jason Williams; 22.08.2010
comment
@bwerks: инициализация новой переменной очень неэффективна и неуклюжа по сравнению с простым обновлением существующей переменной на месте. Особенно, если вы говорите о 2 или 3 переменных. - person Jason Williams; 22.08.2010
comment
Подождите, инициализация новой переменной очень неэффективна, и поэтому лучше использовать ref, где вы должны инициализировать еще до вызова метода? Более того, вы ошибаетесь насчет аута. Ничто не мешает вам передать инициализированную переменную в качестве выходного параметра. Выходы могут быть инициализированы или неинициализированы; ссылки должны быть инициализированы. Единственный раз, когда ref действительно требуется, это когда вы хотите установить для параметра новое значение, полученное из его собственного старого значения. - person bwerks; 24.08.2010
comment
@bwerks: я говорю об обновлении (перезаписи) существующей переменной новым значением (на месте), а не просто о вызове метода один раз для инициализации переменной. Что касается того, что вы можете передать инициализированную переменную без out, попробуйте скомпилировать public void Set(out int a) { a += 7; и вы узнаете, что между out и ref есть довольно важное различие. - person Jason Williams; 24.08.2010
comment
@Jason Williams: да, это не компилируется, но не компилируется, потому что вы пытаетесь обновить переменную, а не инициализировать ее. Это то, о чем говорит Бверкс. Если вам не нужно обновлять переменную, а только инициализировать ее, то нет смысла использовать ref. - person siride; 25.08.2010
comment
@siride: Конечно. Но этот ответ (и вопрос) о том, где/почему вы можете использовать ref — в данном случае для обновления существующей переменной. Мой пример Set() показывает ситуацию, когда вы должны использовать ref, а не out. - person Jason Williams; 25.08.2010

Возможно, когда у вас есть структура (которая является типом значения):

struct Foo
{
    int i;

    public void Test()
    {
        i++;
    }
}

static void update(ref Foo foo)
{
    foo.Test();
}

и

Foo b = new Foo();
update(ref b);

Здесь вы должны использовать два параметра с out, например:

static void update(Foo foo, out Foo outFoo) //Yes I know you could return one foo instead of a out but look below
{
    foo.Test();

    outFoo = foo;
}

отображая метод, имеющий более одного Foo, вы получите вдвое больше параметров с out по сравнению с ref. Альтернативой является возврат N-кортежа. У меня нет реального примера того, когда использовать этот материал.

Добавление: Различные методы .TryParse также могли бы избежать out, если бы вместо этого возвращали Nullable<T>, что по сути является кортежем boolean * T.

person Lasse Espeholt    schedule 21.08.2010
comment
Бьюсь об заклад, этот обнуляемый метод в TryParse превратится в адский метод расширения. - person bwerks; 22.08.2010
comment
Возвращать объект или нуль и ожидать, что вызывающая сторона проверит, рискованно. У нас есть более старые API, которые делают это, и я видел слишком много клиентского кода, который немедленно вызывает методы возвращаемого объекта. Выброс при сбое и/или шаблон TryParse, как правило, являются лучшими вариантами. (TryParse не устраняет глупые ошибки, но препятствует их совершению.) - person TrueWill; 22.08.2010
comment
@bwerks Я сделал это. @TrueWill Возможно, вы правы, но вы должны использовать ??, чтобы пользователи знали. Я использовал метод расширения, такой как: jf (Request["SomeHeader"].ToBoolean() == true), который я действительно считаю хорошим. Это также работает, когда заголовок имеет значение null, но может быть не так ясно, как это работает. - person Lasse Espeholt; 22.08.2010
comment
На самом деле я принял соглашение, связанное с методами try, которые всегда возвращают bool, имеют параметр out и никогда не генерируют исключений; это на самом деле очень удобно. Однако, если подумать, это соглашение нарушается из-за объединения возвращаемого значения и выходного параметра, поскольку null иногда является допустимым ответом, и в этом случае предыдущая форма возвращала null в выходном параметре с возвращаемым логическим значением, равным true. Пример: Dictionary.TryGetValue(key) после Dictionary.Add(key, null). - person bwerks; 24.08.2010

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

person supercat    schedule 22.08.2010

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

person Charles    schedule 21.08.2010
comment
Вы профилировали и определили, что это ваше узкое место в производительности? - person TrueWill; 22.08.2010
comment
@TrueWill: Да, на самом деле. - person Charles; 12.10.2013

Гипотетически я предполагаю, что вы могли бы использовать много аргументов ref/out, если вы намеревались имитировать архитектуру старого процедурного программного обеспечения, например, старых игровых движков и так далее. Я просмотрел исходный код одной из них, думаю, это была Duke Nukem 3D, и она процедурная, с множеством подпрограмм, изменяющих переменные, и почти без функций. Очевидно, что вы вряд ли будете программировать подобное для реального производственного приложения, если у вас нет конкретной цели.

person Tom W    schedule 22.08.2010

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

Пример:

 getTreeNodeValues(ref selectedValue, ref selectedText);

Редактировать:

Лучше использовать здесь - как прокомментировано.

 getTreeNodeValues(out selectedValue, out selectedText);

Я использую его для обработки объектов:

MyCar car = new MyCar { Name="TestCar"; Wieght=1000; }

UpdateWeight(ref car, 2000);
person Andreas Rehm    schedule 21.08.2010
comment
Вы должны использовать ключевое слово out при возврате нескольких значений. ref здесь действительно неуместно. - person Jeff Mercado; 22.08.2010
comment
Вы обязательно должны использовать out, как указано. Но также следует рассмотреть вариант использования вспомогательного объекта. Если у вас есть более двух возвращаемых значений, это, как правило, более чистая стратегия. - person Timwi; 22.08.2010
comment
Я думаю, что это хороший пример утверждения the difference between out and ref parameters is not widely understood, сделанного в ответе dtb. - person Heinzi; 22.08.2010
comment
В моем случае я передаю объекты и меняю в них данные. Мой пример здесь был бы лучше без out. - person Andreas Rehm; 22.08.2010
comment
@Andreas: Является ли MyCar классом (а не структурой)? Если да, то я почти уверен, что вам не нужно ключевое слово ref здесь. Это не требуется для изменения свойств объекта на основе класса (= ссылочный тип). - person Heinzi; 23.08.2010
comment
Как говорит @Heinzi, если вы просто хотите изменить состояние ссылочного типа, вам не следует использовать ключевое слово ref, оно не нужно. Если вы используете ref для параметра ссылочного типа, ваш метод может фактически заменить весь объект совершенно новым объектом или установить для него значение null. Если вам это действительно не нужно, это вводит в заблуждение вызывающих абонентов и может привести к запутанным ошибкам. - person Ash; 23.08.2010

Еще один полезный пример в дополнение к swap‹>:

Prompter.getString("Name ? ", ref firstName);
Prompter.getString("Lastname ? ", ref lastName);
Prompter.getString("Birthday ? ", ref firstName);
Prompter.getInt("Id ? ", ref id);
Prompter.getChar("Id type: <n = national id, p = passport, d = driver licence, m = medicare> \n? ", ref c);



public static class Prompter
{
    public static void getKey(string msg, ref string key)
    {
        Console.Write(msg);
        ConsoleKeyInfo cki = Console.ReadKey();
        string k = cki.Key.ToString();
        if (k.Length == 1)
            key = k;
    }

    public static void getChar(string msg, ref char key)
    {
        Console.Write(msg);
        key = Console.ReadKey().KeyChar;
        Console.WriteLine();
    }

    public static void getString(string msg, ref string s)
    {
        Console.Write(msg);
        string input = Console.ReadLine();
        if (input.Length != 0)
            s = input;
    }

    public static void getInt(string msg, ref int i)
    {
        int result;
        string s;

        Console.Write(msg);
        s = Console.ReadLine();

        int.TryParse(s, out result);
        if (result != 0)
            i = result;       
    }

    // not implemented yet
    public static string getDate(string msg)
    {
        // I should use DateTime.ParseExact(dateString, format, provider);
        throw new NotImplementedException();
    }    


}

Используйте здесь, это не вариант

person boctulus    schedule 15.04.2015