В C #, где вы используете “ref” перед параметром?

StackOverflow https://stackoverflow.com/questions/804196

  •  03-07-2019
  •  | 
  •  

Вопрос

Уже есть ряд вопросов по определению параметров "ref" и "out", но они кажутся плохим дизайном.Есть ли какие-либо случаи, когда вы считаете ref правильным решением?

Кажется, что вы всегда могли бы сделать что-то другое, что было бы чище.Кто-нибудь может привести мне пример того, где это было бы "лучшим" решением проблемы?

Это было полезно?

Решение

На мой взгляд, ref в значительной степени компенсируется трудность объявления новых служебных типов и трудность "привязки информации" к существующей информации, а это те проблемы, к решению которых C # предпринял огромные шаги с момента своего появления с помощью LINQ, generics и анонимных типов.

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

Я действительно думаю, что это все еще имеет смысл (как упоминалось выше) в случае, когда вам нужно вернуть какой-то код ошибки из функции, а также возвращаемое значение, но ничего больше (так что больший тип на самом деле не оправдан.) Если бы я делал это повсеместно в проекте, я бы, вероятно, определил некоторый общий тип оболочки для thing-plus-error-code, но в любом данном экземпляре ref и out все в порядке.

Другие советы

Что ж, ref обычно используется для специализированных случаев, но я бы не назвал его избыточным или устаревшей функцией C #.Вы увидите это (и out) часто используется, например, в XNA.В XNA, a Matrix является struct и при этом довольно массивный (я полагаю, 64 байта), и, как правило, лучше, если вы передадите его функциям, используя ref чтобы не копировать 64 байта, а только 4 или 8.Специальная функция C #?Конечно.От него больше нет особой пользы или это указывает на плохой дизайн?Я не согласен.

Одна из областей заключается в использовании небольших служебных функций, таких как :

void Swap<T>(ref T a, ref T b) { T tmp = a; a = b; b = tmp; }  

Я не вижу здесь никаких "более чистых" альтернатив.Конечно, это не совсем уровень архитектуры.

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

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

void GetXYZ( ref object x, ref object y, ref object z);

Редактировать: диво предложенное использование OUT parameters было бы более подходящим для этого.Я должен признать, что в его словах есть смысл.Я оставлю этот ответ здесь, поскольку, для протокола, это нецелесообразное решение.В этом случае АУТ превосходит REF.

Я думаю, что лучшее применение - это то, что вы обычно видите;у вас должно быть как значение, так и "индикатор успеха", который не является исключением из функции.

Один шаблон проектирования, в котором ref полезен двунаправленный посетитель.

Предположим, у вас был Storage класс, который может использоваться для загрузки или сохранения значений различных примитивных типов.Это либо в Load режим или Save режим.В нем есть группа перегруженных методов, называемых Transfer, и вот пример для работы с int ценности.

public void Transfer(ref int value)
{
    if (Loading)
        value = ReadInt();
    else
        WriteInt(value);
}

Аналогичные методы были бы применены и для других примитивных типов - bool, string, и т.д.

Затем для класса, который должен быть "переносимым", вы бы написали метод, подобный этому:

public void TransferViaStorage(Storage s)
{
    s.Transfer(ref _firstName);
    s.Transfer(ref _lastName);
    s.Transfer(ref _salary);
}

Этот же единственный метод может либо загружать поля из Storage, или сохраните поля в Storage, в зависимости от того , в каком режиме Storage объект находится внутри.

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

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

Сравнение:Атрибуты + Отражение

Зачем делать это вместо присвоения полей и использования отражения для автоматической реализации эквивалента TransferViaStorage?Потому что иногда отражение происходит достаточно медленно, чтобы быть узким местом (но всегда профилируйте, чтобы быть уверенным в этом - это вряд ли когда-либо бывает правдой, а атрибуты намного ближе к идеалу декларативного программирования).

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

Я мог бы посмотреть, есть ли у кого-нибудь структура со 100 значениями (очевидно, это уже проблема), вы, вероятно, захотите передать ее по ссылке, чтобы предотвратить копирование 100 значений.Это, а также возврат этой большой структуры и запись поверх старого значения, вероятно, приведут к проблемам с производительностью.

Очевидная причина использования ключевого слова "ref" заключается в том, что вы хотите передать переменную по ссылке.Например, передача типа значения, такого как System.Int32, методу и изменение его фактического значения.Более конкретное применение может быть, когда вы хотите поменять местами две переменные.

public void Swap(ref int a, ref int b)
{
   ...
}

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

public void DoMagic(out int a, out int b, out int c, out int d)
{
   ...
}

Есть один очевидный случай, когда вы должны использовать ключевое слово 'ref'.Если объект определен, но не создан за пределами области действия метода, который вы собираетесь вызвать, И предполагается, что метод, который вы хотите вызвать, выполняет 'new' чтобы создать его, вы должны использовать 'ref'.например ,{object a; Funct(a);} {Funct(object o) {o = new object; o.name = "dummy";} НИЧЕГО не будет делать с object 'a' он также не будет жаловаться на это ни во время компиляции, ни во время выполнения.Это просто ничего не даст. {object a; Funct(ref a);} {Funct(object ref o) {o = new object(); o.name = "dummy";} приведет к 'a' будучи новым объектом с именем "dummy".Но если 'new' было уже сделано, тогда ссылка не нужна (но работает, если поставлена). {object a = new object(); Funct(a);} {Funct(object o) {o.name = "dummy";}

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top