구조체 형식(C# 참조)

‘구조체 형식’은 데이터와 관련 기능을 캡슐화할 수 있는 값 형식입니다. 구조체 형식은 struct 키워드를 사용하여 정의합니다.

public struct Coords
{
    public Coords(double x, double y)
    {
        X = x;
        Y = y;
    }

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

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

ref structreadonly ref struct 형식에 대한 자세한 내용은 참조 구조체 형식 문서를 참조하세요.

구조체 형식은 ‘값 의미 체계’를 갖습니다. 즉, 구조체 형식의 변수는 해당 형식의 인스턴스를 포함합니다. 기본적으로 변수 값은 할당 시에, 인수를 메서드에 전달할 때, 그리고 메서드 결과를 반환할 때 복사됩니다. 구조체 형식 변수의 경우 해당 형식의 인스턴스가 복사됩니다. 자세한 내용은 값 형식을 참조하세요.

구조체 형식은 일반적으로 동작을 거의 제공하지 않거나 전혀 제공하지 않는 작은 데이터 중심 형식을 설계하는 데 사용합니다. 예를 들어, .NET에서는 구조체 형식을 사용하여 숫자(정수실수), 부울 값, 유니코드 문자, 시간 인스턴스를 표현합니다. 형식의 동작이 중요한 경우에는 클래스를 정의하는 것이 좋습니다. 클래스 형식은 ‘참조 의미 체계’를 갖습니다. 즉, 클래스 형식의 변수는 인스턴스 자체가 아닌 해당 형식의 인스턴스에 대한 참조를 포함합니다.

구조체 형식에는 값 의미 체계가 있기 때문에 변경할 수 없는 구조체 형식을 정의하는 것이 좋습니다.

readonly 구조체

구조체 형식이 변경이 불가능한 것임을 선언하려면 readonly 한정자를 사용합니다. readonly 구조체의 모든 데이터 멤버는 다음과 같이 읽기 전용이어야 합니다.

  • 모든 필드 선언에는 readonly 한정자가 있어야 합니다.
  • 자동 구현 속성을 포함한 모든 속성은 읽기 전용이거나 init 전용이어야 합니다.

이렇게 하면 readonly 구조체의 멤버가 구조체의 상태를 수정하지 않습니다. 이는 생성자를 제외한 다른 인스턴스 멤버가 암시적으로 readonly임을 의미합니다.

참고 항목

readonly 구조체에서 변경 가능한 참조 형식의 데이터 멤버는 여전히 자체 상태를 변경할 수 있습니다. 예를 들어 List<T> 인스턴스를 바꿀 수는 없지만 새 요소를 추가할 수는 있습니다.

다음 코드는 초기화 전용 속성 집합 설정자가 있는 readonly 구조체를 정의합니다.

public readonly struct Coords
{
    public Coords(double x, double y)
    {
        X = x;
        Y = y;
    }

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

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

readonly 인스턴스 멤버

또한 readonly 한정자를 사용하여 인스턴스 멤버가 구조체의 상태를 수정하지 않는다고 선언할 수도 있습니다. 전체 구조체 형식을 readonly로 선언할 수 없는 경우 readonly 한정자를 사용하여 구조체의 상태를 수정하지 않는 인스턴스 멤버를 표시합니다.

readonly 인스턴스 멤버 내에서 구조체의 인스턴스 필드에 할당할 수 없습니다. 그러나 readonly 멤버는readonly가 아닌 멤버를 호출할 수 있습니다. 이 경우 컴파일러는 구조체 인스턴스의 복사본을 만들고 해당 복사본에서 readonly가 아닌 멤버를 호출합니다. 결과적으로 원래 구조체 인스턴스는 수정되지 않습니다.

일반적으로 다음 종류의 인스턴스 멤버에 readonly 한정자를 적용합니다.

  • 메서드:

    public readonly double Sum()
    {
        return X + Y;
    }
    

    System.Object에 선언된 메서드를 재정의하는 메서드에 readonly 한정자를 적용할 수도 있습니다.

    public readonly override string ToString() => $"({X}, {Y})";
    
  • 속성 및 인덱서:

    private int counter;
    public int Counter
    {
        readonly get => counter;
        set => counter = value;
    }
    

    속성 또는 인덱서의 두 접근자에 모두 readonly 한정자를 적용해야 하는 경우 속성 또는 인덱서의 선언에 해당 한정자를 적용합니다.

    참고 항목

    컴파일러는 속성 선언에 readonly 한정자가 있는지 여부와 관계없이 자동 구현 속성get 접근자를 readonly로 선언합니다.

    init 접근자를 사용하여 속성이나 인덱서에 readonly 한정자를 적용할 수 있습니다.

    public readonly double X { get; init; }
    

구조체 형식의 정적 필드에는 readonly 한정자를 적용할 수 있지만 속성이나 메서드와 같은 다른 정적 멤버에는 적용할 수 없습니다.

컴파일러는 성능 최적화를 위해 readonly 한정자를 사용할 수 있습니다. 자세한 내용은 할당 방지를 참조하세요.

비파괴적 변경

C# 10부터 with을 사용하여 지정된 속성과 필드가 수정된 구조체 형식 인스턴스의 복사본을 생성할 수 있습니다. 다음 예제와 같이 개체 이니셜라이저 구문을 사용하여 수정할 멤버와 새 값을 지정합니다.

public readonly struct Coords
{
    public Coords(double x, double y)
    {
        X = x;
        Y = y;
    }

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

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

public static void Main()
{
    var p1 = new Coords(0, 0);
    Console.WriteLine(p1);  // output: (0, 0)

    var p2 = p1 with { X = 3 };
    Console.WriteLine(p2);  // output: (3, 0)

    var p3 = p1 with { X = 1, Y = 4 };
    Console.WriteLine(p3);  // output: (1, 4)
}

record 구조체

C# 10부터 레코드 구조체 형식을 정의할 수 있습니다. 레코드 종류는 데이터 캡슐화를 위한 기본 제공 기능을 제공합니다. record structreadonly record struct 형식을 모두 정의할 수 있습니다. 레코드 구조체는 ref struct일 수 없습니다. 자세한 내용과 예를 보려면 레코드를 참조하세요.

인라인 배열

C# 12부터 인라인 배열struct 형식으로 선언할 수 있습니다.

[System.Runtime.CompilerServices.InlineArray(10)]
public struct CharBuffer
{
    private char _firstElement;
}

인라인 배열은 동일한 형식의 N개 요소로 구성된 연속 블록을 포함하는 구조체입니다. 이는 안전하지 않은 코드에서만 사용할 수 있는 고정 버퍼 선언과 동등한 안전 코드입니다. 인라인 배열은 다음 특성을 가진 struct입니다.

  • 여기에는 단일 필드가 포함됩니다.
  • 구조체는 명시적인 레이아웃을 지정하지 않습니다.

또한 컴파일러는 System.Runtime.CompilerServices.InlineArrayAttribute 특성의 유효성을 검사합니다.

  • 길이는 0보다 커야 합니다(> 0).
  • 대상 유형은 구조체여야 합니다.

대부분의 경우 인라인 배열은 배열처럼 액세스하여 값을 읽고 쓸 수 있습니다. 또한 범위인덱스 연산자를 사용할 수 있습니다.

단일 필드의 형식에는 최소한의 제한이 있습니다. 포인터 형식일 수는 없지만 참조 형식이나 값 형식은 될 수 있습니다. 거의 모든 C# 데이터 구조에서 인라인 배열을 사용할 수 있습니다.

인라인 배열은 고급 언어 기능입니다. 이는 인라인 연속 요소 블록이 다른 대체 데이터 구조보다 빠른 고성능 시나리오를 위한 것입니다. 기능 사양에서 인라인 배열에 대해 자세히 알아볼 수 있습니다.

구조체 초기화 및 기본값

struct 형식의 변수에는 해당 struct에 대한 데이터가 직접 포함됩니다. 이는 기본값을 갖는 초기화되지 않은 struct와 이를 구성하여 설정된 값을 저장하는 초기화된 struct 사이에 차이를 만듭니다. 예를 들어, 다음과 같은 코드를 생각해 볼 수 있습니다.

public readonly struct Measurement
{
    public Measurement()
    {
        Value = double.NaN;
        Description = "Undefined";
    }

    public Measurement(double value, string description)
    {
        Value = value;
        Description = description;
    }

    public double Value { get; init; }
    public string Description { get; init; }

    public override string ToString() => $"{Value} ({Description})";
}

public static void Main()
{
    var m1 = new Measurement();
    Console.WriteLine(m1);  // output: NaN (Undefined)

    var m2 = default(Measurement);
    Console.WriteLine(m2);  // output: 0 ()

    var ms = new Measurement[2];
    Console.WriteLine(string.Join(", ", ms));  // output: 0 (), 0 ()
}

앞의 예에서 볼 수 있듯이 기본값 식은 매개 변수가 없는 생성자를 무시하고 구조체 형식의 기본값을 생성합니다. 또한 구조체 형식 배열 인스턴스화는 매개 변수가 없는 생성자를 무시하고 구조체 형식의 기본값으로 채워진 배열을 생성합니다.

기본값이 표시되는 가장 일반적인 상황은 배열이나 내부 스토리지에 변수 블록이 포함된 다른 컬렉션에 있습니다. 다음 예에서는 각각 기본값이 있는 30개의 TemperatureRange 구조체 배열을 만듭니다.

// All elements have default values of 0:
TemperatureRange[] lastMonth = new TemperatureRange[30];

struct 형식은 해당 데이터를 직접 저장하므로 구조체의 모든 멤버 필드는 만들 때 명확히 할당되어야 합니다. 구조체의 default 값은 모든 필드를 0으로 명확히 할당했습니다. 생성자가 호출될 때 모든 필드는 명확히 할당되어야 합니다. 다음 메커니즘을 사용하여 필드를 초기화합니다.

  • 모든 필드 또는 자동 구현 속성에 필드 이니셜라이저를 추가할 수 있습니다.
  • 생성자 본문에서 모든 필드 또는 자동 속성을 초기화할 수 있습니다.

C# 11부터 구조체의 모든 필드를 초기화하지 않으면 컴파일러는 해당 필드를 기본값으로 초기화하는 코드를 생성자에 추가합니다. 컴파일러는 일반적인 명확한 할당 분석을 수행합니다. 할당되기 전에 액세스되거나 생성자의 실행이 완료될 때 명확히 할당되지 않은 모든 필드에는 생성자 본문이 실행되기 전에 기본값이 할당됩니다. 모든 필드가 할당되기 전에 this에 액세스하면 생성자 본문이 실행되기 전에 구조체가 기본값으로 초기화됩니다.

public readonly struct Measurement
{
    public Measurement(double value)
    {
        Value = value;
    }

    public Measurement(double value, string description)
    {
        Value = value;
        Description = description;
    }

    public Measurement(string description)
    {
        Description = description;
    }

    public double Value { get; init; }
    public string Description { get; init; } = "Ordinary measurement";

    public override string ToString() => $"{Value} ({Description})";
}

public static void Main()
{
    var m1 = new Measurement(5);
    Console.WriteLine(m1);  // output: 5 (Ordinary measurement)

    var m2 = new Measurement();
    Console.WriteLine(m2);  // output: 0 ()

    var m3 = default(Measurement);
    Console.WriteLine(m3);  // output: 0 ()
}

모든 struct에는 매개 변수가 없는 public 생성자가 있습니다. 매개 변수 없는 생성자를 작성하는 경우 public이어야 합니다. 구조체가 필드 이니셜라이저를 선언하는 경우 생성자를 명시적으로 선언해야 합니다. 해당 생성자는 매개 변수가 없을 필요가 없습니다. 구조체가 필드 이니셜라이저를 선언했지만 생성자는 선언하지 않은 경우 컴파일러는 오류를 보고합니다. 명시적으로 선언된 생성자(매개 변수 포함 또는 매개 변수 없음)는 해당 구조체에 대한 모든 필드 이니셜라이저를 실행합니다. 필드 이니셜라이저나 생성자 할당이 없는 모든 필드는 기본값으로 설정됩니다. 자세한 내용은 매개 변수가 없는 구조체 생성자 기능 제안 메모를 참조하세요.

C# 12부터 struct 형식은 선언의 일부로 기본 생성자를 정의할 수 있습니다. 기본 생성자는 해당 구조체의 모든 멤버 선언에서 struct 본문 전체에 사용할 수 있는 생성자 매개 변수에 대한 간결한 구문을 제공합니다.

구조체 형식의 모든 인스턴스 필드가 액세스 가능한 경우, new 연산자 없이 인스턴스화할 수도 있습니다. 이 경우 인스턴스를 처음 사용하기 전에 모든 인스턴스 필드를 초기화해야 합니다. 다음 예제에서는 해당 작업을 수행하는 방법을 보여줍니다.

public static class StructWithoutNew
{
    public struct Coords
    {
        public double x;
        public double y;
    }

    public static void Main()
    {
        Coords p;
        p.x = 3;
        p.y = 4;
        Console.WriteLine($"({p.x}, {p.y})");  // output: (3, 4)
    }
}

기본 제공 값 형식의 경우, 해당 리터럴을 사용하여 해당 형식의 값을 지정합니다.

구조체 형식 설계의 제한 사항

구조체에는 클래스 형식의 기능이 대부분 있습니다. 몇 가지 예외가 있으며 최신 버전에서는 제거된 몇 가지 예외가 있습니다.

  • 구조체 형식은 다른 클래스 또는 구조체 형식에서 상속할 수 없으며, 클래스의 기본이 될 수 없습니다. 단, 구조체 형식은 인터페이스를 구현할 수 있습니다.
  • 구조체 형식 내에서 종료자를 선언할 수 없습니다.
  • C# 11 이전에는 구조체 형식의 생성자가 해당 형식의 모든 인스턴스 필드를 초기화해야 합니다.

참조를 통해 구조체 형식 변수 전달

구조체 형식 변수를 메서드에 인수로 전달하거나 메서드에서 구조체 형식 값을 반환할 경우, 구조체 형식의 인스턴스 전체가 복사됩니다. 값 전달은 대규모 구조체 형식이 포함된 고성능 시나리오에서 코드 성능에 영향을 미칠 수 있습니다. 구조체 형식 변수를 참조를 통해 전달하면 값이 복사되지 않도록 할 수 있습니다. 참조로 인수를 전달해야 한다는 사실을 나타내려면 ref, out, in 또는 ref readonly 메서드 매개 변수 한정자를 사용합니다. 참조를 통해 메서드 결과를 반환하려면 ref returns를 사용합니다. 자세한 내용은 할당 방지를 참조하세요.

구조체 제약 조건

struct 제약 조건struct 키워드를 사용하여 형식 매개 변수가 null을 허용하지 않는 값 형식이라고 지정할 수도 있습니다. 구조체 형식과 열거형 형식 모두 struct 제약 조건을 충족합니다.

변환

모든 구조체 형식(ref struct 형식 제외)에는 System.ValueTypeSystem.Object 형식의 boxing 및 unboxing 변환이 있습니다. 구조체 형식과 구조체 형식이 구현하는 인터페이스 간에도 boxing 및 unboxing 변환이 있습니다.

C# 언어 사양

자세한 내용은 C# 언어 사양구조체 섹션을 참조하세요.

struct 기능에 대한 자세한 내용은 다음 기능 제안 노트를 참조하세요.

참고 항목