Написание безопасного и эффективного кода C#Write safe and efficient C# code

Новые возможности в C# позволяют создавать проверяемый безопасный код с более высокой производительностью.New features in C# enable you to write verifiable safe code with better performance. Если вы будете внимательно применять эти методы, у вас будет меньше сценариев, требующих небезопасного кода.If you carefully apply these techniques, fewer scenarios require unsafe code. Эти функции упрощают использование ссылок на типы значений в качестве аргументов метода и возвращаемых значений метода.These features make it easier to use references to value types as method arguments and method returns. При безопасном выполнении эти методики сводят к минимуму копирование типов значений.When done safely, these techniques minimize copying value types. Используя типы значений, можно свести к минимуму число распределений и сборок мусора.By using value types, you can minimize the number of allocations and garbage collection passes.

В большей части примера кода в этой статье демонстрируются функции, добавленные в C# 7.2.Much of the sample code in this article uses features added in C# 7.2. Чтобы их использовать, нужно настроить проект для работы с языком C# 7.2 или более поздней версии.To use those features, you must configure your project to use C# 7.2 or later. Дополнительные сведения об установке версии языка см. в разделе Настройка языковой версии.For more information on setting the language version, see configure the language version.

Эта статья посвящена методам эффективного управления ресурсами.This article focuses on techniques for efficient resource management. Преимущество использования типов значений заключается в том, что они часто позволяют избежать выделения памяти в кучах.One advantage to using value types is that they often avoid heap allocations. Недостаток состоит в том, что они копируются по значению.The disadvantage is that they're copied by value. Этот компромисс усложняет оптимизацию алгоритмов, работающих с большими объемами данных.This trade-off makes it harder to optimize algorithms that operate on large amounts of data. Новые возможности языка в C# 7.2 предоставляют механизмы, обеспечивающие безопасный и эффективный код со ссылками на типы значений.New language features in C# 7.2 provide mechanisms that enable safe efficient code using references to value types. При рациональном использовании этих функций можно свести к минимуму число операций выделения и копирования.Use these features wisely to minimize both allocations and copy operations. В этой статье приводится описание этих новых функций.This article explores those new features.

Эта статья описывает следующие методы управления ресурсами:This article focuses on the following resource management techniques:

  • Объявите readonly struct, чтобы указать, что тип — неизменяемый.Declare a readonly struct to express that a type is immutable. Это позволяет компилятору сохранять защищенные копии при использовании параметров in.That enables the compiler to save defensive copies when using in parameters.
  • Если тип не может быть неизменяемым, объявите члены struct readonly, чтобы указать, что член не изменяет состояние.If a type can't be immutable, declare struct members readonly to indicate that the member doesn't modify state.
  • Используйте ref readonly, если возвращаемое значение — struct больше, чем IntPtr.Size, и время существования хранилища больше, чем метод, возвращающий значение.Use a ref readonly return when the return value is a struct larger than IntPtr.Size and the storage lifetime is greater than the method returning the value.
  • Если размер readonly struct больше, чем IntPtr.Size, необходимо передать его как параметр in для повышения производительности.When the size of a readonly struct is bigger than IntPtr.Size, you should pass it as an in parameter for performance reasons.
  • Никогда не передавайте struct как параметр in, если он не объявлен с модификатором readonly или метод вызывает только члены readonly структуры.Never pass a struct as an in parameter unless it's declared with the readonly modifier or the method calls only readonly members of the struct. Нарушение этого правила может негативно сказаться на производительности и привести к непонятному поведению.Violating this guidance may negatively affect performance and could lead to an obscure behavior.
  • Используйте ref struct или readonly ref struct, например Span<T> или ReadOnlySpan<T>, для работы с памятью как последовательностью байтов.Use a ref struct, or a readonly ref struct such as Span<T> or ReadOnlySpan<T> to work with memory as a sequence of bytes.

Эти методы помогают найти баланс между двумя противоположными целями в отношении ссылок и значений.These techniques force you to balance two competing goals with regard to references and values. Переменные, которые являются ссылочными типами, содержат ссылку на расположение в памяти.Variables that are reference types hold a reference to the location in memory. Переменные, которые являются типами значений, непосредственно содержат значение.Variables that are value types directly contain their value. Это ключевые различия, которые важны для управления ресурсами памяти.These differences highlight the key differences that are important for managing memory resources. Типы значений обычно копируются при передаче в метод или возвращаются из метода.Value types are typically copied when passed to a method or returned from a method. Это поведение включает в себя копирование значения this при вызове членов типа значения.This behavior includes copying the value of this when calling members of a value type. Издержки копирования связаны с размером типа.The cost of the copy is related to the size of the type. Ссылочные типы размещаются в управляемой куче.Reference types are allocated on the managed heap. Каждый новый объект требует новое распределение и впоследствии должен быть освобожден.Each new object requires a new allocation, and subsequently must be reclaimed. Обе эти операции занимают время.Both these operations take time. Ссылка копируется, когда ссылочный тип передается в качестве аргумента в метод или возвращается из метода.The reference is copied when a reference type is passed as an argument to a method or returned from a method.

В этой статье используется следующий пример концепции трехмерной структуры для объяснения этих рекомендаций:This article uses the following example concept of the 3D-point structure to explain these recommendations:

public struct Point3D
{
    public double X;
    public double Y;
    public double Z;
}

Различные примеры использования других реализаций этой концепции.Different examples use different implementations of this concept.

Объявление структур только для чтения для неизменяемых типов значенийDeclare readonly structs for immutable value types

Объявление struct с помощью модификатора readonly сообщает компилятору, что ваша цель — создать неизменяемый тип.Declaring a struct using the readonly modifier informs the compiler that your intent is to create an immutable type. Компилятор указывает это решение со следующими правилами:The compiler enforces that design decision with the following rules:

  • Все члены поля должны быть readonlyAll field members must be readonly
  • Все свойства должны быть только для чтения, включая автоматически реализуемые свойства.All properties must be read-only, including auto-implemented properties.

Этих двух правил достаточно, чтобы убедиться, что ни один из элементов readonly struct не изменяет состояние этой структуры.These two rules are sufficient to ensure that no member of a readonly struct modifies the state of that struct. Объект struct является неизменяемым.The struct is immutable. Структура Point3D может быть определена как неизменяемая, как показано в следующем примере:The Point3D structure could be defined as an immutable struct as shown in the following example:

readonly public struct ReadonlyPoint3D
{
    public ReadonlyPoint3D(double x, double y, double z)
    {
        this.X = x;
        this.Y = y;
        this.Z = z;
    }

    public double X { get; }
    public double Y { get; }
    public double Z { get; }
}

Следуйте этим рекомендациям, когда планируете создать неизменяемый тип значения.Follow this recommendation whenever your design intent is to create an immutable value type. Улучшения производительности являются дополнительным преимуществом.Any performance improvements are an added benefit. readonly struct четко выражает ваше намерение.The readonly struct clearly expresses your design intent.

Объявление членов, доступных только для чтения, если структура не может быть неизменяемойDeclare readonly members when a struct can't be immutable

В C# 8.0 и более поздних версиях, когда тип структуры является изменяемым, следует объявить члены, которые не приводят к тому, что изменение имеет статус readonly.In C# 8.0 and later, when a struct type is mutable, you should declare members that don't cause mutation to be readonly. Рассмотрим другое приложение, для которого требуется структура объемных точек, однако оно должно поддерживать изменяемость.Consider a different application that needs a 3D point structure, but must support mutability. Следующая версия структуры объемных точек добавляет модификатор readonly только к тем элементам, которые не изменяют структуру.The following version of the 3D point structure adds the readonly modifier only to those members that don't modify the structure. Используйте этот пример, если проект должен поддерживать модификации структуры некоторыми членами, однако вам все равно нужны преимущества реализации через "только для чтения" для некоторых членов:Follow this example when your design must support modifications to the struct by some members, but you still want the benefits of enforcing readonly on some members:

public struct Point3D
{
    public Point3D(double x, double y, double z)
    {
        _x = x;
        _y = y;
        _z = z;
    }

    private double _x;
    public double X
    {
        readonly get => _x;
        set => _x = value;
    }

    private double _y;
    public double Y
    {
        readonly get => _y;
        set => _y = value;
    }

    private double _z;
    public double Z
    {
        readonly get => _z;
        set => _z = value;
    }

    public readonly double Distance => Math.Sqrt(X * X + Y * Y + Z * Z);

    public readonly override string ToString() => $"{X}, {Y}, {Z}";
}

В предыдущем примере показаны многие расположения, в которых можно применить модификатор readonly: методы, свойства и методы доступа к свойствам.The preceding sample shows many of the locations where you can apply the readonly modifier: methods, properties, and property accessors. При использовании автоматически реализуемых свойств компилятор добавляет модификатор readonly к методу доступа get для свойств, предназначенных для чтения и записи.If you use auto-implemented properties, the compiler adds the readonly modifier to the get accessor for read-write properties. Компилятор добавляет модификатор readonly к автоматически реализуемым объявлениям свойств для свойств только с методом доступа get.The compiler adds the readonly modifier to the auto-implemented property declarations for properties with only a get accessor.

Добавление модификатора readonly к членам, которые не изменяют состояние, предоставляет два связанных преимущества.Adding the readonly modifier to members that don't mutate state provides two related benefits. Во первых, компилятор применяет свое намерение.First, the compiler enforces your intent. Этот член не может изменять состояние структуры.That member can't mutate the struct's state. Во-вторых, компилятор не создает защитные копии параметров in при доступе к члену readonly.Second, the compiler won't create defensive copies of in parameters when accessing a readonly member. Компилятор может безопасно выполнить эту оптимизацию, так как она гарантирует, что struct не изменяется членом readonly.The compiler can make this optimization safely because it guarantees that the struct is not modified by a readonly member.

Используйте операторы ref readonly return для больших структур, когда это возможноUse ref readonly return statements for large structures when possible

Вы можете возвращать значения по ссылке, когда возвращаемое значение не является локальным для возвращающего метода.You can return values by reference when the value being returned isn't local to the returning method. Возврат по ссылке означает, что копируется только ссылка, не структура.Returning by reference means that only the reference is copied, not the structure. В следующем примере свойство Origin не может использовать возврат ref, так как возвращаемое значение является локальной переменной:In the following example, the Origin property can't use a ref return because the value being returned is a local variable:

public Point3D Origin => new Point3D(0,0,0);

Тем не менее, следующее определение свойства может возвращаться по ссылке, так как возвращаемое значение является статическим элементом:However, the following property definition can be returned by reference because the returned value is a static member:

public struct Point3D
{
    private static Point3D origin = new Point3D(0,0,0);

    // Dangerous! returning a mutable reference to internal storage
    public ref Point3D Origin => ref origin;

    // other members removed for space
}

Вы не хотите, чтобы вызывающие объекты изменяли источник, поэтому следует возвращать значение через ref readonly:You don't want callers modifying the origin, so you should return the value by ref readonly:

public struct Point3D
{
    private static Point3D origin = new Point3D(0,0,0);

    public static ref readonly Point3D Origin => ref origin;

    // other members removed for space
}

Возвращение ref readonly позволяет сохранить копирование больших структур и неизменность внутренних элементов данных.Returning ref readonly enables you to save copying larger structures and preserve the immutability of your internal data members.

Во время вызова вызывающие объекты выбирают использовать свойство Origin как ref readonly или как значение:At the call site, callers make the choice to use the Origin property as a ref readonly or as a value:

var originValue = Point3D.Origin;
ref readonly var originReference = ref Point3D.Origin;

При первом назначении в предыдущем примере кода создается и назначается копия константы Origin.The first assignment in the preceding code makes a copy of the Origin constant and assigns that copy. При втором назначается ссылка.The second assigns a reference. Обратите внимание, что модификатор readonly должен быть частью объявления переменной.Notice that the readonly modifier must be part of the declaration of the variable. Ссылку, на которую он ссылается, изменить невозможно.The reference to which it refers can't be modified. В противном случае возникнет ошибка времени компиляции.Attempts to do so result in a compile-time error.

В объявлении originReference требуется модификатор readonly.The readonly modifier is required on the declaration of originReference.

Компилятор применяет правило, не позволяющее вызывающему объекту изменять ссылку.The compiler enforces that the caller can't modify the reference. Попытки назначить значение напрямую вызывают ошибку времени компиляции.Attempts to assign the value directly generate a compile-time error. Однако компилятору не может быть известно, изменяет ли какой-либо метод члена состояние структуры.However, the compiler can't know if any member method modifies the state of the struct. Чтобы защитить объект от изменений, компилятор создает копию и с ее помощью вызывает ссылки на члены.To ensure that the object isn't modified, the compiler creates a copy and calls member references using that copy. Все изменения будут вноситься в эту защитную копию.Any modifications are to that defensive copy.

Применение модификатора in к параметрам readonly struct, больше чем System.IntPtr.SizeApply the in modifier to readonly struct parameters larger than System.IntPtr.Size

При передаче аргументов по ссылке можно использовать ключевое слово in как дополнение к существующим ключевым словам ref и out.The in keyword complements the existing ref and out keywords to pass arguments by reference. Ключевое слово in указывает, что аргумент передается по ссылке, но вызванный метод не изменяет это значение.The in keyword specifies passing the argument by reference, but the called method doesn't modify the value.

Это дополнение позволяет полностью выразить намерение разработки.This addition provides a full vocabulary to express your design intent. Если в сигнатуре метода не указан ни один из следующих модификаторов, типы значений копируются при передаче в вызываемый метод.Value types are copied when passed to a called method when you don't specify any of the following modifiers in the method signature. Каждый из этих модификаторов указывает, что переменная передается по ссылке, предотвращая копирование.Each of these modifiers specifies that a variable is passed by reference, avoiding the copy. Каждый модификатор выражает конкретное намерение.Each modifier expresses a different intent:

  • out. этот метод задает значение аргумента, используемого в качестве этого параметра.out: This method sets the value of the argument used as this parameter.
  • ref. этот метод может задавать значение аргумента, используемого в качестве этого параметра.ref: This method may set the value of the argument used as this parameter.
  • in. этот метод не изменяет значение аргумента, используемого в качестве этого параметра.in: This method doesn't modify the value of the argument used as this parameter.

При добавлении модификатора in для передачи аргумента по ссылке вы объявляете о своем намерении передавать аргументы по ссылке, чтобы избежать ненужных операций копирования.Add the in modifier to pass an argument by reference and declare your design intent to pass arguments by reference to avoid unnecessary copying. Вы не собираетесь изменять объект, используемый в качестве этого аргумента.You don't intend to modify the object used as that argument.

Такой подход часто повышает производительность для типов значений только для чтения, размер которых превышает IntPtr.Size.This practice often improves performance for readonly value types that are larger than IntPtr.Size. Для простых типов (sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal и bool и enum) возможное повышение производительности минимально.For simple types (sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal and bool, and enum types), any potential performance gains are minimal. На самом деле, производительность может снизиться при использовании передачи по ссылке для типов, меньше чем IntPtr.Size.In fact, performance may degrade by using pass-by-reference for types smaller than IntPtr.Size.

Ниже приведен пример метода, который вычисляет расстояние между двумя точками в трехмерном пространстве.The following code shows an example of a method that calculates the distance between two points in 3D space.

private static double CalculateDistance(in Point3D point1, in Point3D point2)
{
    double xDifference = point1.X - point2.X;
    double yDifference = point1.Y - point2.Y;
    double zDifference = point1.Z - point2.Z;

    return Math.Sqrt(xDifference * xDifference + yDifference * yDifference + zDifference * zDifference);
}

Аргументами являются две структуры, каждая из которых содержит три типа double.The arguments are two structures that each contain three doubles. double имеет размер 8 байт, поэтому каждый аргумент равен 24 байтам.A double is 8 bytes, so each argument is 24 bytes. Указывая модификатор in, вы, в зависимости от архитектуры компьютера, передаете этим аргументам 4- или 8-байтовую ссылку.By specifying the in modifier, you pass a 4 byte or 8-byte reference to those arguments, depending on the architecture of the machine. Разница в размере невелика, но она может вырасти, когда приложение вызывает этот метод в непрерывном цикле с помощью множества различных значений.The difference in size is small, but it adds up when your application calls this method in a tight loop using many different values.

Существуют и другие способы, которыми модификатор in дополняет out и ref.The in modifier complements out and ref in other ways as well. Невозможно создать перегрузки метода, которые отличаются только наличием in, out или ref.You can't create overloads of a method that differ only in the presence of in, out, or ref. Эти новые правила расширяют то же поведение, которое всегда действовало для параметров out и ref.These new rules extend the same behavior that had always been defined for out and ref parameters. Как и модификаторы out и ref, типы значений не упаковываются, так как применяется модификатор in.Like the out and ref modifiers, value types aren't boxed because the in modifier is applied.

Модификатор in может применяться к любому члену, который принимает параметры: к методам, делегатам, лямбда-выражениям, локальным функциям, индексаторам, операторам.The in modifier may be applied to any member that takes parameters: methods, delegates, lambdas, local functions, indexers, operators.

Еще одно преимущество параметров in состоит в том, что вы можете использовать литеральные значения или константы для аргумента в параметре in.Another feature of in parameters is that you may use literal values or constants for the argument to an in parameter. Кроме того, в отличие от параметра ref или out, не нужно применять модификатор in на сайте вызова.Also, unlike a ref or out parameter, you don't need to apply the in modifier at the call site. В следующем коде показаны два примера вызова метода CalculateDistance.The following code shows you two examples of calling the CalculateDistance method. В первом используются две локальные переменные, передаваемые по ссылке.The first uses two local variables passed by reference. Второй содержит временную переменную, созданную в рамках вызова метода.The second includes a temporary variable created as part of the method call.

var distance = CalculateDistance(pt1, pt2);
var fromOrigin = CalculateDistance(pt1, new Point3D());

Существует несколько способов, когда компилятор гарантирует, что аргумент in доступен только для чтения.There are several ways in which the compiler enforces the read-only nature of an in argument. Во-первых, вызванный метод не может быть напрямую назначен параметру in.First of all, the called method can't directly assign to an in parameter. Его невозможно напрямую назначить полю параметра in, когда это значение имеет тип struct.It can't directly assign to any field of an in parameter when that value is a struct type. Кроме того, параметр in невозможно передать какому-либо методу, использующему модификатор ref или out.In addition, you can't pass an in parameter to any method using the ref or out modifier. Эти правила применяются к любому полю параметра in при условии, что данное поле имеет тип struct и параметр имеет тип struct.These rules apply to any field of an in parameter, provided the field is a struct type and the parameter is also a struct type. На самом деле эти правила применяются к нескольким уровням доступа к членам при условии, что все уровни доступа к членам являются structs.In fact, these rules apply for multiple layers of member access provided the types at all levels of member access are structs. Компилятор принудительно указывает, что типы struct, передаваемые в качестве аргументов in, и их члены struct являются переменными, доступными только для чтения, когда используются в качестве аргументов для других методов.The compiler enforces that struct types passed as in arguments and their struct members are read-only variables when used as arguments to other methods.

Использование параметров in позволяет избежать потенциальных затрат на создание копий.The use of in parameters can avoid the potential performance costs of making copies. Это не меняет семантику ни одного вызова метода.It doesn't change the semantics of any method call. Таким образом, указывать модификатор in в месте вызова не нужно.Therefore, you don't need to specify the in modifier at the call site. Пропуск модификатора in в месте вызова сообщает компилятору, что он может сделать копию аргумента по следующим причинам:Omitting the in modifier at the call site informs the compiler that it's allowed to make a copy of the argument for the following reasons:

  • Выполняется неявное преобразование, но не преобразование удостоверения из типа аргумента в тип параметра.There exists an implicit conversion but not an identity conversion from the argument type to the parameter type.
  • Аргумент является выражением, но не имеет известную переменную хранения.The argument is an expression but doesn't have a known storage variable.
  • Существует перегрузка, которая отличается наличием или отсутствием in.An overload exists that differs by the presence or absence of in. В этом случае перегрузка по значению подходит лучше.In that case, the by value overload is a better match.

Эти правила полезны и при обновлении существующего кода для использования аргументов со ссылками, доступными только для чтения.These rules are useful as you update existing code to use read-only reference arguments. Внутри вызываемого метода можно вызвать любой метод экземпляра, который использует параметры передачи по значению.Inside the called method, you can call any instance method that uses by value parameters. В этих экземплярах создается копия параметра in.In those instances, a copy of the in parameter is created. Поскольку компилятор может создавать временную переменную для любого параметра in, вы можете также указать значения по умолчанию для любого параметра in.Because the compiler may create a temporary variable for any in parameter, you can also specify default values for any in parameter. Следующий код указывает начало координат (точку 0,0) в качестве значения по умолчанию для второй точки:The following code specifies the origin (point 0,0) as the default value for the second point:

private static double CalculateDistance2(in Point3D point1, in Point3D point2 = default)
{
    double xDifference = point1.X - point2.X;
    double yDifference = point1.Y - point2.Y;
    double zDifference = point1.Z - point2.Z;

    return Math.Sqrt(xDifference * xDifference + yDifference * yDifference + zDifference * zDifference);
}

Чтобы велеть компилятору передавать аргументы, доступные только для чтения, по ссылке, укажите модификатор in для аргументов в месте вызова, как показано в следующем коде:To force the compiler to pass read-only arguments by reference, specify the in modifier on the arguments at the call site, as shown in the following code:

distance = CalculateDistance(in pt1, in pt2);
distance = CalculateDistance(in pt1, new Point3D());
distance = CalculateDistance(pt1, in Point3D.Origin);

Это упрощает постепенное внедрение параметров in в больших базах кода, где возможен выигрыш по производительности.This behavior makes it easier to adopt in parameters over time in large codebases where performance gains are possible. Сначала нужно добавить модификатор in в сигнатуры методов.You add the in modifier to method signatures first. Затем можно добавить модификатор in в местах вызовов и создать типы readonly struct, чтобы разрешить компилятору не создавать защитные копии параметров in в дополнительных расположениях.Then, you can add the in modifier at call sites and create readonly struct types to enable the compiler to avoid creating defensive copies of in parameters in more locations.

Обозначение параметра in также можно использовать со ссылочными типами или числовыми значениями.The in parameter designation can also be used with reference types or numeric values. Однако преимущества в обоих случаях минимальны (если они вообще есть).However, the benefits in both cases are minimal, if any.

Не используйте изменяемые структуры как аргумент inAvoid mutable structs as an in argument

Методики, описанные выше, объясняют, как избежать копий путем возврата ссылок и передачи значения по ссылке.The techniques described above explain how to avoid copies by returning references and passing values by reference. Эти методы лучше всего работают, когда типы аргументов объявляются как типы readonly struct.These techniques work best when the argument types are declared as readonly struct types. В противном случае компилятор должен создавать защитные копии во многих ситуациях, чтобы гарантировать, что все аргументы будут доступны только для чтения.Otherwise, the compiler must create defensive copies in many situations to enforce the readonly-ness of any arguments. Рассмотрим следующий пример, который вычисляет расстояние до точки в трехмерном пространстве от начала координат:Consider the following example that calculates the distance of a 3D point from the origin:

private static double CalculateDistance(in Point3D point1, in Point3D point2)
{
    double xDifference = point1.X - point2.X;
    double yDifference = point1.Y - point2.Y;
    double zDifference = point1.Z - point2.Z;

    return Math.Sqrt(xDifference * xDifference + yDifference * yDifference + zDifference * zDifference);
}

Структура Point3Dне предоставляется только для чтения.The Point3D structure is not a readonly struct. В тексте этого метода есть шесть разных вызовов доступа к свойству.There are six different property access calls in the body of this method. На первый взгляд может показаться, что эти доступы безопасны.On first examination, you may have thought these accesses were safe. В конце концов, метод доступа get не должен изменять состояние объекта.After all, a get accessor shouldn't modify the state of the object. Но нет правила языка, которое это обеспечивает.But there's no language rule that enforces that. Это просто общее соглашение.It's only a common convention. Любой тип может реализовывать метод доступа get, который изменил внутреннее состояние.Any type could implement a get accessor that modified the internal state. Без языковой гарантии компилятору необходимо создавать временную копию аргумента перед вызовом любого члена, не помеченного модификатором readonly.Without some language guarantee, the compiler must create a temporary copy of the argument before calling any member not marked with the readonly modifier. Временное хранилище создается на стеке, значения аргумента копируются во временном хранилище, и значение копируется в стек для каждого доступа к членам как аргумент this.The temporary storage is created on the stack, the values of the argument are copied to the temporary storage, and the value is copied to the stack for each member access as the this argument. Во многих случаях эти копии достаточно снижают производительность, так что передача по значению работает быстрее, чем передача по ссылке только для чтения, если тип аргумента не readonly struct и метод вызывает члены, не помеченные как readonly.In many situations, these copies harm performance enough that pass-by-value is faster than pass-by-readonly-reference when the argument type isn't a readonly struct and the method calls members that aren't marked readonly. Если пометить все методы, которые не изменяют состояние структуры как readonly, компилятор сможет безопасно определить, что состояние структуры не изменено и защитная копия не требуется.If you mark all methods that don't modify the struct state as readonly, the compiler can safely determine that the struct state isn't modified, and a defensive copy is not needed.

Вместо этого, если вычисление расстояния использует неизменяемую структуру, ReadonlyPoint3D, временные объекты не требуются:Instead, if the distance calculation uses the immutable struct, ReadonlyPoint3D, temporary objects aren't needed:

private static double CalculateDistance3(in ReadonlyPoint3D point1, in ReadonlyPoint3D point2 = default)
{
    double xDifference = point1.X - point2.X;
    double yDifference = point1.Y - point2.Y;
    double zDifference = point1.Z - point2.Z;

    return Math.Sqrt(xDifference * xDifference + yDifference * yDifference + zDifference * zDifference);
}

Компилятор создает более эффективный код, когда вызываются члены readonly struct: Ссылка this, а не копия приемника, всегда является параметром in, переданным по ссылке методу члена.The compiler generates more efficient code when you call members of a readonly struct: The this reference, instead of a copy of the receiver, is always an in parameter passed by reference to the member method. Эта оптимизация позволяет избежать копирования при использовании readonly struct в качестве аргумента in.This optimization saves copying when you use a readonly struct as an in argument.

Не следует передавать тип значения, допускающий значения NULL, в качестве аргумента in.You shouldn't pass a nullable value type as an in argument. Тип Nullable<T> не объявлен как структура только для чтения.The Nullable<T> type isn't declared as a read-only struct. Это означает, что компилятор должен создавать защитные копии для любого аргумента типа, допускающего значение NULL и передаваемого в метод с помощью модификатора in в объявлении параметра.That means the compiler must generate defensive copies for any nullable value type argument passed to a method using the in modifier on the parameter declaration.

Вы видите пример программы, который демонстрирует разницу в производительности с помощью BenchmarkDotNet в наших репозиториях примеров на сайте GitHub.You can see an example program that demonstrates the performance differences using BenchmarkDotNet in our samples repository on GitHub. Он сравнивает передачу изменяемых структур по значению и по ссылке с передачей неизменяемых структур по значению и по ссылке.It compares passing a mutable struct by value and by reference with passing an immutable struct by value and by reference. Быстрее всего использовать неизменяемую структуру и передачу по ссылке.The use of the immutable struct and pass by reference is fastest.

Используйте типы ref struct для работы с блоками или памятью в одном кадре стекаUse ref struct types to work with blocks or memory on a single stack frame

Еще одной связанной функцией языка является возможность объявлять тип значения, который должен быть ограничен одним кадром стека.A related language feature is the ability to declare a value type that must be constrained to a single stack frame. Это ограничение позволяет компилятору кое-что оптимизировать.This restriction enables the compiler to make several optimizations. Главным стимулом для создания этой функции была структура Span<T> и связанные структуры.The primary motivation for this feature was Span<T> and related structures. Вы получите повышение производительности благодаря этим усовершенствованиям, если будете использовать новые и обновленные интерфейсы API .NET, которые используют тип Span<T>.You'll achieve performance improvements from these enhancements by using new and updated .NET APIs that make use of the Span<T> type.

Похожие требования могут иметь место при работе с памятью, созданной с помощью stackalloc, или при использовании памяти из API взаимодействия.You may have similar requirements working with memory created using stackalloc or when using memory from interop APIs. Для этих задач можно определить собственные типы ref struct.You can define your own ref struct types for those needs.

Тип readonly ref structreadonly ref struct type

Объявление структуры как readonly ref сочетает в себе преимущества и недостатки объявлений ref struct и readonly struct.Declaring a struct as readonly ref combines the benefits and restrictions of ref struct and readonly struct declarations. Объем памяти, используемой диапазоном только для чтения, будет ограничен одним кадром стека, а объем памяти, используемой диапазоном только для чтения, невозможно изменить.The memory used by the readonly span is restricted to a single stack frame, and the memory used by the readonly span can't be modified.

ВыводыConclusions

Использование типов значений сводит к минимуму число операций распределения:Using value types minimizes the number of allocation operations:

  • Хранилище для типов значений выделяется в стеке для локальных переменных и аргументов метода.Storage for value types is stack allocated for local variables and method arguments.
  • Хранилище для типов значений, которые являются членами других объектов, выделяется как часть этого объекта, а не отдельное распределение.Storage for value types that are members of other objects is allocated as part of that object, not as a separate allocation.
  • Хранилища возвращаемых значений типов значений выделяется в стеке.Storage for value type return values is stack allocated.

Сравните это со ссылочными типами в таких же ситуациях:Contrast that with reference types in those same situations:

  • Хранилище для ссылочных типов выделяется в куче для локальных переменных и аргументов метода.Storage for reference types are heap allocated for local variables and method arguments. Ссылка хранится в стеке.The reference is stored on the stack.
  • Хранилище для ссылочных типов, которые являются членами других объектов, выделяется в куче отдельно.Storage for reference types that are members of other objects are separately allocated on the heap. Объект хранит ссылку.The containing object stores the reference.
  • Хранилище возвращаемых значений ссылочного типа выделяется в куче.Storage for reference type return values is heap allocated. Ссылка на это хранилище хранится в стеке.The reference to that storage is stored on the stack.

Чтобы свести распределения к минимуму, придется пойти на компромисс.Minimizing allocations comes with tradeoffs. Вы копируете больше памяти, если размер struct больше, чем размер ссылки.You copy more memory when the size of the struct is larger than the size of a reference. Ссылка обычно является 64- или 32-разрядной и зависит от ЦП целевого компьютера.A reference is typically 64 bits or 32 bits, and depends on the target machine CPU.

Эти компромиссы обычно имеют минимальное влияние на производительность.These tradeoffs generally have minimal performance impact. Однако для больших структур или больших коллекций влияние на производительность возрастает.However, for large structs or larger collections, the performance impact increases. Влияние может быть большим в плотных циклах и часто используемых путях для программ.The impact can be large in tight loops and hot paths for programs.

Эти усовершенствования языка C# предназначены для критических алгоритмов производительности, когда минимизация распределений памяти может иметь большое значение для достижения необходимой производительности.These enhancements to the C# language are designed for performance critical algorithms where minimizing memory allocations is a major factor in achieving the necessary performance. Может оказаться, что в создаваемом коде эти функции используются довольно редко.You may find that you don't often use these features in the code you write. Тем не менее эти усовершенствования были реализованы в .NET.However, these enhancements have been adopted throughout .NET. Поскольку с этими функциями работает все больше API-интерфейсов, повышение производительности приложений не останется незаметным.As more and more APIs make use of these features, you'll see the performance of your applications improve.

См. такжеSee also