안전하고 효율적인 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.
  • 반환 값이 IntPtr.Size보다 큰 struct이고 스토리지 수명이 값을 반환하는 메서드보다 클 경우 ref readonly 반환을 사용합니다.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.
  • readonly 한정자로 선언되었거나 메서드가 구조체의 readonly 멤버만 호출하는 경우를 제외하고 structin 매개 변수로 전달하면 안 됩니다.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.

이 문서에서는 이러한 권장 사항을 설명하기 위해 3D 요소 구조체의 다음 예제 개념을 사용합니다.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

readonly 한정자를 사용하여 struct를 선언하면 변경할 수 없는 형식을 만들려는 의도를 컴파일러에 알립니다.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:

  • 모든 필드 멤버는 readonly여야 합니다.All 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.

구조체를 변경할 수 없는 경우 readonly 멤버 선언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. 3D 요소 구조체가 필요하지만 가변성을 지원해야 하는 다른 애플리케이션을 생각해 보겠습니다.Consider a different application that needs a 3D point structure, but must support mutability. 다음 버전의 3D 요소 구조체는 구조체를 수정하지 않는 멤버에만 readonly 한정자를 추가합니다.The following version of the 3D point structure adds the readonly modifier only to those members that don't modify the structure. 디자인에서 일부 멤버의 구조체에 대한 수정을 지원해야 하는 경우 이 예제를 따릅니다. 하지만 여전히 일부 멤버에 readonly를 적용하는 이점이 필요할 수 있습니다.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. 자동 구현 속성을 사용하는 경우, 컴파일러에서 읽기/쓰기 속성의 get 접근자에 readonly 한정자를 추가합니다.If you use auto-implemented properties, the compiler adds the readonly modifier to the get accessor for read-write properties. 컴파일러는 get 접근자만 있는 속성의 자동 구현 속성 선언에 readonly 한정자를 추가합니다.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. 둘째, 컴파일러에서 readonly 멤버에 액세스할 때 in 매개 변수의 방어형 복사본을 만들지 않습니다.Second, the compiler won't create defensive copies of in parameters when accessing a readonly member. 컴파일러는 readonly 멤버가 struct를 수정하지 않도록 하여 이 최적화를 안전하게 지원할 수 있습니다.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.

readonly 한정자는 originReference 선언에 필요합니다.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 한정자를 System.IntPtr.Size보다 큰 readonly struct 매개 변수에 적용Apply the in modifier to readonly struct parameters larger than System.IntPtr.Size

in 키워드는 기존 refout 키워드를 보완하여 참조로 인수를 전달합니다.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, boolenum 형식)의 경우 모든 잠재적 성능 향상이 최소화됩니다.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.

다음 코드는 3D 공간에서 두 점 사이의 거리를 계산하는 메서드의 예를 보여 줍니다.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 한정자는 outref를 다른 방식으로도 보완합니다.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. 이러한 새 규칙은 항상 outref 매개 변수에 대해 정의된 같은 동작을 확장합니다.These new rules extend the same behavior that had always been defined for out and ref parameters. outref 한정자와 마찬가지로 in 한정자가 적용되므로 값 형식이 boxing되지 않습니다.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. 값이 struct 형식일 때 in 매개 변수의 모든 필드에 직접 할당할 수 없습니다.It can't directly assign to any field of an in parameter when that value is a struct type. 또한 ref 또는 out 한정자를 사용하여 모든 메서드에 in 매개 변수를 전달할 수 없습니다.In addition, you can't pass an in parameter to any method using the ref or out modifier. 이러한 규칙은 필드가 struct 형식이고 매개 변수 또한 struct 형식인 경우 in 매개 변수의 모든 필드에 적용됩니다.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. 컴파일러는 in 인수로 전달된 struct 형식과 그 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:

  • 암시적 변환은 있지만 인수 유형에서 매개 변수 유형으로의 ID 변환이 아닙니다.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. 이 경우에는 by 값 오버로드가 더 적합합니다.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. 호출된 메서드 내에서 by 값 매개 변수를 사용하는 모든 인스턴스 메서드를 호출할 수 있습니다.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.

변경 가능한 구조체를 in 인수로 사용하지 마세요.Avoid 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. 원점에서 3D 요소의 거리를 계산하는 다음과 같은 예제를 살펴보세요.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. 이 메서드의 본문에는 6개의 서로 다른 속성 액세스 호출이 있습니다.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 structin 인수로 사용하는 경우 복사본을 저장합니다.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. 즉 컴파일러가 매개 변수 선언에서 in 수정자를 사용하여 메서드에 전달된 모든 null 허용 값 형식 인수에 대해 방어용 복사본을 생성해야 합니다.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.

GitHub에 있는 샘플 리포지토리에서 BenchmarkDotNet을 사용하여 성능 차이를 설명하는 예제 프로그램을 확인할 수 있습니다.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. Span<T> 유형을 사용하는 새롭고 업데이트된 .NET API를 사용함으로써 이러한 향상된 기능으로부터 성능 향상을 달성할 수 있습니다.You'll achieve performance improvements from these enhancements by using new and updated .NET APIs that make use of the Span<T> type.

stackalloc을 사용하여 만들어진 메모리로 작업할 때나 interop 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 struct 형식readonly ref struct type

구조체를 readonly ref로 선언하면 ref structreadonly 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비트이며 대상 머신 CPU에 따라 달라집니다.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