Introduction
struct
(구조체) 는 객체 및 메서드를 캡슐화 할 수 있는 값 형식이다.struct
키워드로 정의하며, .NET 기본 값 형식 (int
,bool
...) 등이 구조체로 구성되어있다.값 형식
이므로 할당, 메서드 인수 전달 및 리턴 시 인스턴스가 복사된다.struct
는 인스턴스가 하나의 값을 의미하므로, 내부 데이터를 변경할 수 없는 형식으로 정의하는 것이 좋다.
값 형식
이기 때문에, 작은 크기의 읽기용 데이터 구조를 만드는 데 주로 사용한다. (데이터 자체보다 데이터의 거동이 중요한 경우, 참조 형식인 클래스가 더 적합하다)
struct 정의
struct
는 기본적으로 다음과 같이 정의한다.public struct SerialCommunicationInformation { public int COMPortNumber; public int Baudrate; public System.IO.Ports.Parity Parity; public int Databits; public System.IO.Ports.StopBits Stopbits; public System.IO.Ports.Handshake Handshake; }
struct
일부 또는 전체를 변경할 수 없게 하는 경우readonly
키워드를 붙여 사용한다.// struct 일부 public struct CartesianCoordinate { public float X { get; set; } public readonly float Y { get; } public float Y2 { get; init; } // C# 9.0. 위 멤버 Y와 동일한 기능이다. (생성자를 제외한 곳에서 값 수정 불가) public CartesianCoordinate(float X, float Y) { this.X = X; this.Y = Y; } } // struct 전체 public readonly struct CartesianCoordinate { public float X { get; init; } public float Y { get; init; } }
struct
가 Heap 상에 존재하는 것을 방지하기 위해, 아래와 같이ref
키워드를 사용할 수 있다.public ref struct CartesianCoordinate { public float X { get; init; } public ref float Y; // C# 11. 프로퍼티 구현 시 setter는 할 수 없다. public CartesianCoordinate(float x, ref float y) { X = x; Y = ref y; } public override string ToString() => $"{X}, {Y}"; } static void Main(string[] args) { float y = 20; var coordinate = new CartesianCoordinate(10, ref y); Console.WriteLine(coordinate.ToString()); } /* output: 10, 20 */
record에
struct
키워드를 추가하여 값 형식의 record를 생성 수 있다. (C# 10)public record struct CartesianCoordinate { public float X { get; init; } public float Y { get; init; } }
C# 12 버전부터는 record처럼 선언 시 기본 생성자를 정의할 수 있다.
기본 생성자의 인수에 대해 자동으로 필드를 생성해주지는 않는다.
추가로 생성자를 정의하는 경우, 반드시 기본 생성자를 경유해야한다.
public struct CartesianCoordinate(int x, int y) { public int X { get; init; } = x; public int Y { get; init; } = y; public CartesianCoordinate(int x) : this(x, default) { } }
struct 초기화
struct
는 다음과 같이 초기화 할 수 있다public struct CartesianCoordinate { public float X { get; init; } public float Y { get; init; } public CartesianCoordinate(float X, float Y) { this.X = X; this.Y = Y; } } var coordinate = new CartesianCoordinate() { X = 10, Y = 20 }; var coordinate2 = new CartesianCoordinate(10, 20);
struct
의 필드가 접근 가능한 경우, 조건부로new
연산자 없이 인스턴스 생성이 가능하다.모든 필드 또는 일부 필드가 접근 가능한 경우, 할당한 필드만 값을 얻어올 수 있다.
모든 필드가 접근 가능하고 모든 필드를 할당한 경우,
struct
객체가 생성된다.public struct CartesianCoordinate { public float X; public float Y; private float Z; public override string ToString() => $"{X}, {Y}, {Z}"; } static void Main() { CartesianCoordinate coordinate; coordinate.X = 10; coordinate.Y = 20; Console.WriteLine(coordinate.X); // 10 Console.WriteLine(coordinate.Y); // 20 Console.WriteLine(coordinate); // CS0165 }
public struct CartesianCoordinate { public float X; public float Y; public float Z; public override string ToString() => $"{X}, {Y}, {Z}"; } static void Main() { CartesianCoordinate coordinate; coordinate.X = 10; coordinate.Y = 20; coordinate.Z = 30; Console.WriteLine(coordinate.X); // 10 Console.WriteLine(coordinate.Y); // 20 Console.WriteLine(coordinate.Z); // 30 Console.WriteLine(coordinate); // 10, 20, 30 }
C# 10 버전부터는 파라미터가 없는 생성자 및 필드 이니셜라이저를 정의할 수 있다.
public struct CartesianCoordinate { public float X { get; init; } = 10; public float Y { get; init; } = 20; public CartesianCoordinate() { X = 10; Y = 20; } }
C# 10 버전부터
with
식이 도입되어 구조체의 일부를 수정한 인스턴스 복사가 가능하다.public struct CartesianCoordinate { public float X { get; init; } public float Y { get; init; } } var coordinate = new CartesianCoordinate(); Console.WriteLine($"{coordinate.X}, {coordinate.Y}"); // 0, 0 var coordinate1 = coordinate with { X = 1 }; Console.WriteLine($"{coordinate1.X}, {coordinate1.Y}"); // 1, 0
C# 11 버전부터는 초기화되지 않은 필드에 대해 컴파일러가 초기화 코드를 추가해준다.
public struct CartesianCoordinate { public float X { get; init; } public float Y { get; init; } public CartesianCoordinate(float x) { X = x; // Y = default; 컴파일러가 추가 } }
(주의) default 키워드를 이용한 초기화 시, 기본 생성자가 무시되며 각 멤버의 기본값으로 초기화된다.
public struct CartesianCoordinate { public float X { get; init; } public float Y { get; init; } public CartesianCoordinate() { X = 10; Y = 20; } } var coordinate = new CartesianCoordinate(); Console.WriteLine($"{coordinate.X}, {coordinate.Y}"); // 10, 20 var coordinate2 = default(CartesianCoordinate); Console.WriteLine($"{coordinate2.X}, {coordinate2.Y}"); // 0, 0
인라인 배열
- C# 12 버전부터
struct
에 인라인 배열을 사용할 수 있다.- Unsafe 코드에서의 고정 크기 버퍼 동등하다 : 고정 크기 버퍼의 safe 버전
struct
선언부에 System.Runtime.CompilerServices.InlineArrayAttribute 특성을 부여하여 사용한다.
- 인라인 배열은 다음과 같은 특징을 갖고 있다.
- 필드는 1개만 가질 수 있으며, 배열과 같이 접근한다.
- 범위 연산자
..
, 인덱서를 사용할 수 있다. - 인라인 배열 특성이 추가되는 구조체는 Explicit 레이아웃을 지정할 수 없다.
- System.Span<T>, System.ReadOnlySpan<T>과 변환이 가능하다.
[System.Runtime.CompilerServices.InlineArray(5)]
public struct StructArray
{
private float _value;
}
static void Main(string[] args)
{
var array = new StructArray();
array[2] = 10;
array[4] = 20;
ReadOnlySpan<float> span = array; // Length 얻기 위해 span 변환
Console.WriteLine(span.Length);
Console.WriteLine();
foreach (var value in span)
{
Console.WriteLine(value);
}
}
/* output:
5
0
0
10
0
20
*/