Обложка канала

.NET Разработчик

Опытный разработчик не так давно зашёл в .Net и поставил цель получить сертификат Microsoft. Свой ежедневный прогресс он описывает на канале .Net Разработчик. Заметки об изученном материале, советы по повышению производительности и поддержке мотивации, ин

.NET Разработчик

4 года назад
Открыть в
День 1460. #ЗаметкиНаПолях Ref-поля в C#11 Ключевое слово ref указывает, что переменная передаётся по ссылке. Это позволяет вызываемой функции изменить значение переменной в вызывающем её коде. C# поддерживает ref для аргумента метода или возврата метода, а также для локальной переменной:
int i = 10;

Foo(ref i);

ref int Foo(ref int j)
{
 ref int k = ref i;
 return ref j;
}

В этом примере параметр j и локальная переменная k являются псевдонимами для переменной i. Когда вы получаете их значения, читается i, а когда устанавливаете - устанавливается значение i. Ref-переменные могут указывать как на память на стеке, что подразумевает строгие правила области видимости, так и на память в куче, которой управляет сборщик мусора. Компилятор сохраняет область видимости, т.е. разрешено возвращать ref j, потому что компилятор знает, что переменная, на которую ссылается параметр, всё ещё будет в области видимости. Компилятор не позволит вам вернуть ссылку на k, потому что локальная ссылка может указывать на стековую память, которая выйдет за пределы области видимости при возврате из метода. C#8 представил ref-структуры и использовал их для реализации Span. Тип Span — это абстракция в стеке для области памяти стека или кучи. Для представления памяти Span нужны два поля: ref-поле, указывающее на первый элемент памяти, и поле для хранения длины блока памяти. До C#11 ref-поле было внутренней реализацией среды выполнения. В C#11 ref-поля открыты для общего использования. Они позволяют вам определить ваши собственные ref-структуры имеющие ref-поля. Например, вы можете создать тип, читающий информацию из большой структуры, которая может находиться в стеке, в куче или даже в неуправляемой памяти:
struct MyStruct { 
 /* большая структура */
}

ref struct StructReader
{
  ref MyStruct _value;
  public StructReader(ref MyStruct value) 
    => _value = ref value;

  public void ReadValue(Span<byte> value)
  { … }
  …
}

При передаче ссылок в методы ref-структуры компилятор гарантирует, что переменные, на которые вы ссылаетесь, не выйдут за пределы области видимости раньше, чем сама структура. В противном случае ref-структура могла бы ссылаться на переменные вне области видимости:
void Process(ref StructReader reader)
{
  Span<byte> buffer = stackalloc byte[10];

  reader.ReadSomeValue(buffer);
}

Здесь будет выдана ошибка CS8352: Cannot use variable 'buffer' in this context because it may expose referenced variables outside of their declaration scope (Невозможно использовать переменную 'buffer' в этом контексте, потому что она может раскрыть переменные, на которые она ссылается, за пределы области их объявления). Вы можете указать компилятору разрешить передачу этих переменных, добавив ключевое слово scoped. Тогда компилятор запретит сохранять такие параметры в структуре:
public void ReadSomeValue(
 scoped Span<byte> value
)
{ … }

Т.е. ключевое слово scoped указывает компилятору обращаться с аргументом value так же, как если бы он был локальной переменной в методе. Источник: https://developers.redhat.com/articles/2023/01/11/5-new-advanced-features-improving-c-11#ref_fields