В С#, где вы используете "ref" перед параметром?
Уже существует ряд вопросов по определению параметров "ref" и "out", но они кажутся плохими. Есть ли случаи, когда вы считаете правильным решение?
Кажется, вы всегда можете сделать что-то еще, что чище. Может ли кто-нибудь дать мне пример того, где это будет "лучшее" решение проблемы?
Ответы
Ответ 1
По-моему, ref
в значительной степени компенсировал трудность объявления новых типов утилиты и трудность "привязки информации" к существующей информации, которая является тем, что С# предпринял огромные шаги для решения с момента своего генезиса через LINQ, дженериков и анонимных типов.
Так что нет, я не думаю, что для этого есть много явных вариантов использования. Я думаю, что это в значительной степени реликвия того, как изначально был разработан язык.
Я действительно думаю, что он все еще имеет смысл (как упоминалось выше) в том случае, когда вам нужно вернуть какой-то код ошибки из функции, а также возвращаемое значение, но ничего больше (поэтому более крупный тип не является действительно оправдано.) Если бы я делал это по всему месту в проекте, я бы, вероятно, определил какой-то общий тип обертки для кода "плюс-ошибка", но в любом данном экземпляре ref
и out
все в порядке.
Ответ 2
Ну, ref
обычно используется для специализированных случаев, но я бы не назвал его избыточным или устаревшей функцией С#. Вы увидите, что он (и out
) использовал много в XNA, например. В XNA a Matrix
является struct
и довольно массивным в этом (я считаю, 64 байта), и обычно лучше всего передать его функциям с помощью ref
, чтобы избежать копирования 64 байтов, но только 4 или 8 Особенность специалиста С#? Безусловно. Из-за плохого использования или плохого дизайна? Я не согласен.
Ответ 3
В одной области используются небольшие функции полезности, например:
void Swap<T>(ref T a, ref T b) { T tmp = a; a = b; b = tmp; }
Я не вижу альтернативы "чище". Конечно, это не совсем уровень архитектуры.
Ответ 4
P/Invoke - единственное место, где я действительно могу думать о месте, где вы должны использовать ref или out. В других случаях они могут быть удобными, но, как вы сказали, обычно есть другой, более чистый способ.
Ответ 5
Что делать, если вы хотите вернуть несколько объектов, которые по неизвестной причине не связаны друг с другом в один объект.
void GetXYZ( ref object x, ref object y, ref object z);
EDIT: divo, предложенный с использованием параметров OUT, будет более подходящим для этого. Должен признаться, он понял. Я оставлю этот ответ здесь, так как, для записи, это решение без ответа. OUT trumps REF в этом случае.
Ответ 6
Я думаю, что лучше всего использовать те, которые вы обычно видите; вам нужно иметь как значение, так и "индикатор успеха", который не является исключением из функции.
Ответ 7
Реальное использование для этого - это когда вы создаете структуру. Структуры в С# являются типами значений и поэтому всегда полностью копируются при передаче по значению. Если вам необходимо передать его по ссылке, например, по соображениям производительности или потому, что функция должна вносить изменения в переменную, вы должны использовать ключевое слово ref.
Я мог видеть, имеет ли кто-нибудь структуру со 100 значениями (очевидно, проблема уже), вы, вероятно, захотите передать ее по ссылке, чтобы предотвратить копирование 100 значений. Это и возврат к тому, что большая структура и запись поверх старого значения, вероятно, будут иметь проблемы с производительностью.
Ответ 8
Один шаблон проектирования, в котором 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
, вы не знаете, будет ли этот метод читать или писать, и это позволяет вам создавать классы посетителей, которые работают в одном из двух направления, которые должны быть вызваны симметричным образом (т.е. с помощью метода посещения, который не должен знать, в каком режиме направления работает класс посетителей).
Сравнение: Атрибуты + Отражение
Почему это вместо того, чтобы приписывать поля и использовать отражение для автоматического выполнения эквивалента TransferViaStorage
? Потому что иногда отражение достаточно медленно, чтобы быть узким местом (но всегда профиль, чтобы быть уверенным в этом - он вряд ли когда-либо верен, а атрибуты гораздо ближе к идеалу декларативного программирования).
Ответ 9
Очевидная причина использования ключевого слова "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)
{
...
}
Ответ 10
Есть один ясный случай, когда вы должны использовать ключевое слово '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'
уже был выполнен, тогда ref не нужен (но работает, если поставлено). {object a = new object(); Funct(a);} {Funct(object o) {o.name = "dummy";}