Структуры с модификатором ref (далее ref-структуры) представляют такие типы, которые размещаются в стеке. В то время как обычные структуры могут быть упакованы в ссылочные типы и помещены в управляемой куче (хипе), ref-структуры
размещаются только в стеке. Это делает ref-структуры удобным инструментом в различных сценариях, где нужна высокая производительность. Среди встроенных типов .NET к ref-структурам, в частности, относят ReadOnlySpan<char>.
Основные особенности ref-структур:
Память под ref-структуры выделяется только в стеке, в кучу они не перемещаются
ref-структуры не могут быть упакованы в ссылочные типы (иначе это потребовало бы размещение такиз структур в куче). Поэтому их нельзя преобразовать в ссылочный тип, например, в тип интерфейса, даже если они реализуют этот интерфейс
ref-структуры характеризуются большей безопасностью, поскольку размещаются в стеке и соответственно не страдают от утечек памяти и других проблем, связанных с управляемой кучей.
В силу предыдущих пунктов часто применяются, где важна скорость и производительность. Поскольку типы ref-структуры всегда выделяются в стеке, выделение и освобождение происходят намного быстрее, чем выделения на основе кучи. Это снижает нагрузку на сборщик мусора, что приводит к более предсказуемой производительности.
Детерминированное время жизни: поскольку объекты ref-структуры выделяются в стеке, их время жизни является детерминированным — они автоматически освобождаются, когда метод, в котором они были созданы, завершает работу. Это делает ref struct идеальным для временных объектов, которым не нужно находиться вне области действия метода
В то же время ref-структуры имеют ряд ограничений:
ref-структура не может представлять элемент массива
ref-структура не может представлять поле класса или обычной структуры
ref-структура не может быть упакована в
System.ValueTypeилиSystem.Objectref-структура не может быть захвачена в лямбда-выражении или локальной функции
Определение ref-структур
ref-структуры определяются как обычные структуры только с модификатором ref. Например:
var tom = new Person("Tom");
Console.WriteLine(tom.Name);
ref struct Person
{
public Person(string name) => Name = name;
public string Name {get;}
}
Более практический пример:
int[] numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9};
Sum summator = new Sum(numbers, 1..3);
Console.WriteLine(summator.Get()); // 5
summator = new Sum(numbers, 2..4);
Console.WriteLine(summator.Get()); // 7
ref struct Sum
{
ReadOnlySpan<int> data;
public Sum(int[] numbers, Range range)
{
data = numbers[range];
}
public int Get()
{
int result = 0;
foreach(var n in data)
{
result += n;
}
return result;
}
}
Здесь структура Sum вычисляет сумму значений коллекции на некотором диапазоне. Диапазон и коллекция в виде массива передаются через конструктор. Диапазон представляет тип Range. В конструкторе получаем нужный диапазон массива в объект
типа ReadOnlySpan<int>. Для передачи диапазона в конструкторе применяется выражение 1..3, то есть получаем иапазон из элементов от 1 индекса до 3-го (не включая). В методе Get в цикле вычисляем сумму значений на указанном диапазоне.
Реализация интерфейсов в ref-структурах
До C# 13 ref-структуры не могли реализовывать интерфейсы. Начиная с C# 13, они могут. Однако, в силу ограничений ref-структур, такая структура не может быть преобразован в тип интерфейса. Это преобразование является преобразованием упаковки и может нарушить безопасность ref. ref-структуры также не могут объявлять методы, которые явно реализуют методы интерфейса. Кроме того, ref-структуры должны реализовывать все методы, объявленные в интерфейсе, включая методы с реализацией по умолчанию. Например:
Shape myShape = new();
myShape.Draw(); // Draw some shape
// Draw_Shape(myShape); // ! Ошибка - Так нельзя
// var shape2 = (IShape)shape; // ! Ошибка - Так нельзя
void Draw_Shape(IShape shape)
{
shape.Draw();
}
interface IShape
{
void Draw();
}
ref struct Shape : IShape
{
public void Draw()
{
Console.WriteLine("Draw some shape");
}
}
Обратите внимание, что мы не можем преобразовать значение ref-структуры (в данном случае Shape) к типу интерфейса (IShape):
var shape2 = (IShape)shapeАналогично мы не можем использовать значение ref-структуры там, где требуется объект интерфейса (так как все равно идет преобразование, только неявно), как в случае с методом Draw_Shape:
// var shape2 = (IShape)shape; // ! Ошибка - Так нельзя
void Draw_Shape(IShape shape)
{
shape.Draw();
}