etc./StackOverFlow

C#에서 클래스 대신 구조체를 사용해야 하는 경우는 언제인가요?

청렴결백한 만능 재주꾼 2022. 2. 24. 22:02
반응형

질문자 :Alex Baranosky


C#에서 클래스가 아닌 구조체를 사용해야 하는 경우는 언제입니까? 내 개념적 모델은 항목이 단순히 값 유형의 모음일 때 구조체가 사용된다는 것입니다. 논리적으로 모든 것을 하나의 응집력 있는 전체로 묶는 방법입니다.

여기 에서 이러한 규칙을 발견했습니다.

  • 구조체는 단일 값을 나타내야 합니다.
  • 구조체의 메모리 공간은 16바이트 미만이어야 합니다.
  • 구조체는 생성 후에 변경하면 안 됩니다.

이러한 규칙이 작동합니까? 구조체는 의미적으로 무엇을 의미합니까?



OP에서 참조한 소스에는 약간의 신뢰성이 있습니다. 하지만 Microsoft는 어떻습니까? 구조체 사용에 대한 입장은 무엇입니까? Microsoft에서 추가 학습을 찾았고 다음과 같이 찾았습니다.

유형의 인스턴스가 작고 일반적으로 수명이 짧거나 일반적으로 다른 개체에 포함되는 경우 클래스 대신 구조를 정의하는 것이 좋습니다.

유형에 다음 특성이 모두 없는 경우 구조를 정의하지 마십시오.

  1. 논리적으로 기본 유형(정수, 이중 등)과 유사한 단일 값을 나타냅니다.
  2. 인스턴스 크기가 16바이트보다 작습니다.
  3. 그것은 불변입니다.
  4. 자주 상자에 넣을 필요는 없습니다.

Microsoft는 이러한 규칙을 지속적으로 위반합니다.

좋아, 어쨌든 #2와 #3. 우리가 사랑하는 사전에는 2개의 내부 구조체가 있습니다.

 [StructLayout(LayoutKind.Sequential)] // default for structs private struct Entry //<Tkey, TValue> { // View code at *Reference Source } [Serializable, StructLayout(LayoutKind.Sequential)] public struct Enumerator : IEnumerator<KeyValuePair<TKey, TValue>>, IDisposable, IDictionaryEnumerator, IEnumerator { // View code at *Reference Source }

* 참조 소스

'JonnyCantCode.com' 소스는 4점 만점에 3점을 얻었습니다. #4는 문제가 되지 않을 것이기 때문에 꽤 용서할 수 있습니다. 구조체를 권투하는 자신을 발견하면 아키텍처를 다시 생각하십시오.

Microsoft가 이러한 구조체를 사용하는 이유를 살펴보겠습니다.

  1. 각 구조체, EntryEnumerator 는 단일 값을 나타냅니다.
  2. 속도
  3. Entry 는 Dictionary 클래스 외부에 매개변수로 전달되지 않습니다. 추가 조사에 따르면 IEnumerable의 구현을 충족하기 위해 Dictionary는 열거자가 요청될 때마다 복사 Enumerator
  4. Dictionary 클래스의 내부입니다. 사전이 열거 가능하고 IEnumerator 인터페이스 구현(예: IEnumerator getter)에 대해 동일한 액세스 가능성을 가져야 하기 때문에 Enumerator

업데이트 - 또한 구조체가 Enumerator와 같이 인터페이스를 구현하고 구현된 유형으로 캐스트될 때 구조체가 참조 유형이 되어 힙으로 이동된다는 점을 알아두십시오. Dictionary 클래스 내부에서 Enumerator 여전히 값 유형입니다. 그러나 메서드가 GetEnumerator() 호출하는 즉시 참조 유형 IEnumerator 가 반환됩니다.

여기서 볼 수 없는 것은 구조체를 불변 상태로 유지하거나 인스턴스 크기를 16바이트 이하로 유지하려는 시도 또는 요구 사항의 증거입니다.

  1. 위의 구조체에서 readonly 으로 선언된 것은 없습니다.
  2. 이 구조체의 크기는 16바이트를 훨씬 초과할 수 있습니다.
  3. Entry 수명이 미정입니다( Add() 에서 Remove() , Clear() 또는 가비지 수집까지).

그리고 ... 4. 두 구조체 모두 참조 유형이 될 수 있다는 것을 알고 있는 TKey 및 TValue를 저장합니다(추가 정보).

해시된 키에도 불구하고 사전은 부분적으로 구조체를 인스턴스화하는 것이 참조 유형보다 빠르기 때문에 빠릅니다. 여기에 순차적으로 증가하는 키가 있는 300,000개의 임의의 정수를 저장 Dictionary<int, int>

용량: 312874
MemSize: 2660827바이트
완료된 크기 조정: 5ms
총 채우기 시간: 889ms

용량 : 내부 배열의 크기를 조정하기 전에 사용할 수 있는 요소의 수입니다.

MemSize : 사전을 MemoryStream으로 직렬화하고 바이트 길이를 가져옴으로써 결정됩니다(목적에 맞게 정확함).

Completed Resize : 내부 배열의 크기를 150862개 요소에서 312874개 요소로 조정하는 데 걸리는 시간입니다. Array.CopyTo() 를 통해 순차적으로 복사된다는 것을 알면 너무 초라하지 않습니다.

총 채우기 시간 : 로깅 및 소스에 추가 OnResize 그러나 작업 중에 15배 크기를 조정하면서 300k 정수를 채우는 것은 여전히 인상적입니다. 그냥 호기심에서, 내가 이미 용량을 알고 있다면 채우는 데 걸리는 총 시간은 얼마입니까? 13ms

Entry 가 클래스라면 어떻게 될까요? 이 시간이나 측정항목이 실제로 그렇게 많이 다를까요?

용량: 312874
MemSize: 2660827바이트
완료된 크기 조정: 26ms
총 채우기 시간: 964ms

분명히 큰 차이는 크기 조정에 있습니다. 사전이 용량으로 초기화되면 어떤 차이가 있습니까? 걱정할 만큼은 아닙니다 ... 12ms .

발생하는 일은 Entry 가 구조체이기 때문에 참조 유형과 같은 초기화가 필요하지 않다는 것입니다. 이것은 가치 유형의 아름다움이자 골칫거리입니다. Entry 를 참조 유형으로 사용하려면 다음 코드를 삽입해야 했습니다.

 /* * Added to satisfy initialization of entry elements -- * this is where the extra time is spent resizing the Entry array * **/ for (int i = 0 ; i < prime ; i++) { destinationArray[i] = new Entry( ); } /* *********************************************** */

Entry 각 배열 요소를 참조 유형으로 초기화해야 했던 이유는 MSDN: Structure Design 에서 찾을 수 있습니다. 간단히 말해서:

구조에 대한 기본 생성자를 제공하지 마십시오.

구조가 기본 생성자를 정의하는 경우 구조의 배열이 생성될 때 공용 언어 런타임은 각 배열 요소에 대해 기본 생성자를 자동으로 실행합니다.

C# 컴파일러와 같은 일부 컴파일러에서는 구조에 기본 생성자가 있는 것을 허용하지 않습니다.

그것은 실제로 매우 간단하며 우리는 Asimov의 로봇 공학의 세 가지 법칙(Three Laws of Robotics) 에서 차용할 것입니다.

  1. 구조체는 사용하기에 안전해야 합니다.
  2. 구조체는 규칙 #1을 위반하지 않는 한 효율적으로 기능을 수행해야 합니다.
  3. 구조체는 규칙 #1을 충족하기 위해 파괴가 필요하지 않는 한 사용하는 동안 그대로 유지되어야 합니다.

... 이것에서 무엇을 빼야 할까요? 간단히 말해서 값 유형의 사용에 책임이 있습니다. 빠르고 효율적이지만 적절하게 유지 관리하지 않으면(예: 의도하지 않은 복사) 많은 예기치 않은 동작을 일으킬 수 있습니다.


IAbstract

언제든지 너를:

  1. 다형성이 필요하지 않습니다.
  2. 가치 의미론을 원하고,
  3. 힙 할당 및 관련 가비지 수집 오버헤드를 피하려고 합니다.

그러나 주의할 점은 구조체(임의로 큼)는 클래스 참조(일반적으로 하나의 기계어)보다 전달하는 데 비용이 더 많이 들기 때문에 실제로는 클래스가 더 빨라질 수 있다는 것입니다.


dsimcha

나는 원래 게시물에 주어진 규칙에 동의하지 않습니다. 내 규칙은 다음과 같습니다.

  1. 배열에 저장할 때 성능을 위해 구조체를 사용합니다. (또한 구조체는 언제 답이 됩니까? 참조 )

  2. C/C++로/에서 구조화된 데이터를 전달하는 코드에 필요합니다.

  3. 필요한 경우가 아니면 구조체를 사용하지 마십시오.

    • 할당 및 인수로 전달할 때 "일반 개체"( 참조 유형 )와 다르게 동작하므로 예기치 않은 동작이 발생할 수 있습니다. 코드를 보는 사람이 자신이 구조체를 다루고 있다는 사실을 모르는 경우 특히 위험합니다.
    • 그들은 상속될 수 없습니다.
    • 구조체를 인수로 전달하는 것은 클래스보다 비용이 많이 듭니다.

ILoveFortran

참조 의미 체계와 반대로 값 의미 체계를 원할 때 구조체를 사용합니다.

편집하다

사람들이 왜 이것을 downvoting하는지 확실하지 않지만 이것은 유효한 요점이며 op가 그의 질문을 명확히하기 전에 작성되었으며 구조체에 대한 가장 근본적인 기본 이유입니다.

참조 의미 체계가 필요한 경우 구조체가 아닌 클래스가 필요합니다.


JoshBerke

은 "이 값은"대답 또한, 구조체를 사용하기위한 하나 개의 특정 시나리오는 당신이 쓰레기 수거 문제를 일으키는 데이터 집합을 알고, 당신은 객체의 많은 때입니다. 예를 들어 Person 인스턴스의 큰 목록/배열이 있습니다. 여기서 자연스러운 은유는 클래스이지만 수명이 긴 Person 인스턴스가 많으면 GEN-2가 막히고 GC 중단이 발생할 수 있습니다. 시나리오가 보증하는 경우 여기에서 한 가지 잠재적인 접근 방식은 Person 구조체 의 배열(목록 아님), 즉 Person[] 입니다. 이제 GEN-2에 수백만 개의 개체가 있는 대신 LOH에 단일 청크가 있습니다(여기에는 문자열 등이 없다고 가정합니다. 즉, 참조가 없는 순수한 값). 이것은 GC 영향이 거의 없습니다.

이 데이터로 작업하는 것은 데이터가 구조체에 비해 크기가 너무 크고 항상 뚱뚱한 값을 복사하고 싶지 않기 때문에 어색합니다. 그러나 배열에서 직접 액세스하면 구조체가 복사되지 않고 제자리에 있습니다(복사를 수행하는 목록 인덱서와 대조됨). 이는 인덱스에 대한 많은 작업을 의미합니다.

 int index = ... int id = peopleArray[index].Id;

값 자체를 변경할 수 없도록 유지하는 것이 여기에서 도움이 됩니다. 더 복잡한 논리의 경우 by-ref 매개변수가 있는 메서드를 사용합니다.

 void Foo(ref Person person) {...} ... Foo(ref peopleArray[index]);

다시 말하지만 이것은 제자리에 있습니다. 값을 복사하지 않았습니다.

매우 구체적인 시나리오에서 이 전술은 매우 성공적일 수 있습니다. 그러나 그것은 당신이 무엇을 하고 있고 왜 하는지 알고 있는 경우에만 시도해야 하는 상당히 발전된 시나리오입니다. 여기서 기본값은 클래스입니다.


Marc Gravell

C# 언어 사양에서 :

1.7 구조체

클래스와 마찬가지로 구조체는 데이터 멤버와 함수 멤버를 포함할 수 있는 데이터 구조이지만 클래스와 달리 구조체는 값 형식이며 힙 할당이 필요하지 않습니다. 구조체 유형의 변수는 구조체의 데이터를 직접 저장하는 반면 클래스 유형의 변수는 동적으로 할당된 개체에 대한 참조를 저장합니다. 구조체 형식은 사용자 지정 상속을 지원하지 않으며 모든 구조체 형식은 개체 형식에서 암시적으로 상속됩니다.

구조체는 값 의미 체계가 있는 작은 데이터 구조에 특히 유용합니다. 복소수, 좌표계의 점 또는 사전의 키-값 쌍은 모두 구조체의 좋은 예입니다. 작은 데이터 구조에 대해 클래스 대신 구조체를 사용하면 애플리케이션이 수행하는 메모리 할당 수에 큰 차이를 만들 수 있습니다. 예를 들어, 다음 프로그램은 100개 점의 배열을 만들고 초기화합니다. Point를 클래스로 구현하면 101개의 개별 객체가 인스턴스화됩니다(배열에 대해 하나씩, 100개 요소에 대해 각각 하나씩).

 class Point { public int x, y; public Point(int x, int y) { this.x = x; this.y = y; } } class Test { static void Main() { Point[] points = new Point[100]; for (int i = 0; i < 100; i++) points[i] = new Point(i, i); } }

대안은 Point를 구조체로 만드는 것입니다.

 struct Point { public int x, y; public Point(int x, int y) { this.x = x; this.y = y; } }

이제 하나의 개체(배열에 대한 개체)만 인스턴스화되고 Point 인스턴스는 배열에 인라인으로 저장됩니다.

구조체 생성자는 new 연산자로 호출되지만 메모리가 할당되고 있다는 의미는 아닙니다. 객체를 동적으로 할당하고 객체에 대한 참조를 반환하는 대신 구조체 생성자는 구조체 값 자체(일반적으로 스택의 임시 위치)를 반환하기만 하면 이 값이 필요에 따라 복사됩니다.

클래스를 사용하면 두 변수가 동일한 개체를 참조할 수 있으므로 한 변수에 대한 작업이 다른 변수에서 참조하는 개체에 영향을 미칠 수 있습니다. 구조체를 사용하면 변수 각각에 자체 데이터 복사본이 있으며 하나의 작업이 다른 하나에 영향을 미칠 수 없습니다. 예를 들어 다음 코드 조각에서 생성된 출력은 Point가 클래스인지 구조체인지에 따라 다릅니다.

 Point a = new Point(10, 10); Point b = a; ax = 20; Console.WriteLine(bx);

Point가 클래스이면 a와 b가 동일한 객체를 참조하기 때문에 출력은 20입니다. Point가 구조체이면 b를 할당하면 값의 복사본이 생성되고 이 복사본은 ax에 대한 후속 할당의 영향을 받지 않기 때문에 출력은 10입니다.

이전 예제에서는 구조체의 두 가지 제한 사항을 강조합니다. 첫째, 전체 구조체를 복사하는 것은 일반적으로 개체 참조를 복사하는 것보다 덜 효율적이므로 할당 및 값 매개변수 전달은 참조 유형보다 구조체에서 더 비쌀 수 있습니다. 둘째, ref 및 out 매개변수를 제외하고는 구조체에 대한 참조를 생성할 수 없으므로 여러 상황에서 사용이 금지됩니다.


Luke Baughan

구조체는 데이터의 원자적 표현에 적합하며, 여기서 해당 데이터는 코드에 의해 여러 번 복사될 수 있습니다. 객체 복제는 일반적으로 구조체를 복사하는 것보다 비용이 더 많이 듭니다. 메모리 할당, 생성자 실행 및 완료 시 할당 해제/가비지 컬렉션이 포함되기 때문입니다.


Franci Penov

다음은 기본 규칙입니다.

  • 모든 멤버 필드가 값 유형인 경우 구조체를 만듭니다.

  • 하나의 멤버 필드가 참조 유형인 경우 클래스를 작성하십시오. 이는 참조 유형 필드에 어쨌든 힙 할당이 필요하기 때문입니다.

 public struct MyPoint { public int X; // Value Type public int Y; // Value Type } public class MyPointWithName { public int X; // Value Type public int Y; // Value Type public string Name; // Reference Type }

Usman Zafar

첫째: Interop 시나리오 또는 메모리 레이아웃을 지정해야 하는 경우

둘째: 어쨌든 데이터가 참조 포인터와 거의 같은 크기일 때.


BC.

일반적으로 PInvoke의 경우 StructLayoutAttribute를 사용하여 메모리 레이아웃을 명시적으로 지정하려는 상황에서 "구조체"를 사용해야 합니다.

편집: 주석은 StructLayoutAttribute와 함께 클래스 또는 구조체를 사용할 수 있으며 이는 확실히 사실이라고 지적합니다. 실제로는 일반적으로 구조체를 사용합니다. 이는 스택 대 힙에 할당되므로 관리되지 않는 메서드 호출에 인수를 전달하는 경우 의미가 있습니다.


Maurice Flanagan

저는 모든 종류의 바이너리 통신 형식을 패킹하거나 풀기 위해 구조체를 사용합니다. 여기에는 디스크에 대한 읽기 또는 쓰기, DirectX 정점 목록, 네트워크 프로토콜 또는 암호화/압축 데이터 처리가 포함됩니다.

당신이 나열한 세 가지 지침은 이 맥락에서 나에게 유용하지 않습니다. 특정 주문에서 400바이트의 내용을 작성해야 할 때 400바이트 구조체를 정의하고 관련되지 않은 값으로 채울 것입니다. 어떤 방식으로든 설정하는 것이 그 당시에 가장 합리적인 방법입니다. (좋아요, 400바이트는 꽤 이상할 것입니다. 하지만 제가 생계를 위해 Excel 파일을 작성할 당시에는 BIFF 레코드 중 일부가 얼마나 큰지 최대 약 40바이트의 구조체를 처리하고 있었습니다.)


mjfgates

런타임 및 PInvoke 목적을 위해 다양한 기타에서 직접 사용되는 값 유형을 제외하고 두 가지 시나리오에서만 값 유형을 사용해야 합니다.

  1. 복사 의미 체계가 필요할 때.
  2. 자동 초기화가 필요할 때 일반적으로 이러한 유형의 배열에서.

leppie

숫자에서 "구조체"의 이점을 더 잘 이해하기 위해 BenchmarkDotNet 으로 작은 벤치마크를 만들었습니다. 구조체(또는 클래스)의 배열(또는 목록)을 통해 루핑을 테스트하고 있습니다. 이러한 배열이나 목록을 만드는 것은 벤치마크의 범위를 벗어납니다. "클래스"가 더 무거우면 더 많은 메모리를 사용하고 GC가 필요하다는 것이 분명합니다.

따라서 결론은 다음과 같습니다. LINQ 및 숨겨진 구조체 boxing/unboxing에 주의하고 미세 최적화에 구조체를 사용하는 것은 엄격하게 배열과 함께 유지됩니다.

PS 호출 스택을 통해 구조체/클래스를 전달하는 것에 대한 또 다른 벤치마크는 https://stackoverflow.com/a/47864451/506147입니다.

 BenchmarkDotNet=v0.10.8, OS=Windows 10 Redstone 2 (10.0.15063) Processor=Intel Core i5-2500K CPU 3.30GHz (Sandy Bridge), ProcessorCount=4 Frequency=3233542 Hz, Resolution=309.2584 ns, Timer=TSC [Host] : Clr 4.0.30319.42000, 64bit RyuJIT-v4.7.2101.1 Clr : Clr 4.0.30319.42000, 64bit RyuJIT-v4.7.2101.1 Core : .NET Core 4.6.25211.01, 64bit RyuJIT Method | Job | Runtime | Mean | Error | StdDev | Min | Max | Median | Rank | Gen 0 | Allocated | ---------------- |----- |-------- |----------:|----------:|----------:|----------:|----------:|----------:|-----:|-------:|----------:| TestListClass | Clr | Clr | 5.599 us | 0.0408 us | 0.0382 us | 5.561 us | 5.689 us | 5.583 us | 3 | - | 0 B | TestArrayClass | Clr | Clr | 2.024 us | 0.0102 us | 0.0096 us | 2.011 us | 2.043 us | 2.022 us | 2 | - | 0 B | TestListStruct | Clr | Clr | 8.427 us | 0.1983 us | 0.2204 us | 8.101 us | 9.007 us | 8.374 us | 5 | - | 0 B | TestArrayStruct | Clr | Clr | 1.539 us | 0.0295 us | 0.0276 us | 1.502 us | 1.577 us | 1.537 us | 1 | - | 0 B | TestLinqClass | Clr | Clr | 13.117 us | 0.1007 us | 0.0892 us | 13.007 us | 13.301 us | 13.089 us | 7 | 0.0153 | 80 B | TestLinqStruct | Clr | Clr | 28.676 us | 0.1837 us | 0.1534 us | 28.441 us | 28.957 us | 28.660 us | 9 | - | 96 B | TestListClass | Core | Core | 5.747 us | 0.1147 us | 0.1275 us | 5.567 us | 5.945 us | 5.756 us | 4 | - | 0 B | TestArrayClass | Core | Core | 2.023 us | 0.0299 us | 0.0279 us | 1.990 us | 2.069 us | 2.013 us | 2 | - | 0 B | TestListStruct | Core | Core | 8.753 us | 0.1659 us | 0.1910 us | 8.498 us | 9.110 us | 8.670 us | 6 | - | 0 B | TestArrayStruct | Core | Core | 1.552 us | 0.0307 us | 0.0377 us | 1.496 us | 1.618 us | 1.552 us | 1 | - | 0 B | TestLinqClass | Core | Core | 14.286 us | 0.2430 us | 0.2273 us | 13.956 us | 14.678 us | 14.313 us | 8 | 0.0153 | 72 B | TestLinqStruct | Core | Core | 30.121 us | 0.5941 us | 0.5835 us | 28.928 us | 30.909 us | 30.153 us | 10 | - | 88 B |

암호:

 [RankColumn, MinColumn, MaxColumn, StdDevColumn, MedianColumn] [ClrJob, CoreJob] [HtmlExporter, MarkdownExporter] [MemoryDiagnoser] public class BenchmarkRef { public class C1 { public string Text1; public string Text2; public string Text3; } public struct S1 { public string Text1; public string Text2; public string Text3; } List<C1> testListClass = new List<C1>(); List<S1> testListStruct = new List<S1>(); C1[] testArrayClass; S1[] testArrayStruct; public BenchmarkRef() { for(int i=0;i<1000;i++) { testListClass.Add(new C1 { Text1= i.ToString(), Text2=null, Text3= i.ToString() }); testListStruct.Add(new S1 { Text1 = i.ToString(), Text2 = null, Text3 = i.ToString() }); } testArrayClass = testListClass.ToArray(); testArrayStruct = testListStruct.ToArray(); } [Benchmark] public int TestListClass() { var x = 0; foreach(var i in testListClass) { x += i.Text1.Length + i.Text3.Length; } return x; } [Benchmark] public int TestArrayClass() { var x = 0; foreach (var i in testArrayClass) { x += i.Text1.Length + i.Text3.Length; } return x; } [Benchmark] public int TestListStruct() { var x = 0; foreach (var i in testListStruct) { x += i.Text1.Length + i.Text3.Length; } return x; } [Benchmark] public int TestArrayStruct() { var x = 0; foreach (var i in testArrayStruct) { x += i.Text1.Length + i.Text3.Length; } return x; } [Benchmark] public int TestLinqClass() { var x = testListClass.Select(i=> i.Text1.Length + i.Text3.Length).Sum(); return x; } [Benchmark] public int TestLinqStruct() { var x = testListStruct.Select(i => i.Text1.Length + i.Text3.Length).Sum(); return x; } }

Roman Pokrovskij

.NET은 value typesreference types 지원합니다(Java에서는 참조 유형만 정의할 수 있음). reference types 인스턴스는 관리되는 힙에 할당되고 해당 인스턴스에 대한 미해결 참조가 없을 때 가비지 수집됩니다. 반면에 value types 인스턴스는 stack 에 할당되므로 할당된 메모리는 해당 범위가 종료되는 즉시 회수됩니다. 물론 value types 은 값으로 전달되고 reference types 은 참조로 전달됩니다. System.String을 제외한 모든 C# 기본 데이터 형식은 값 형식입니다.

클래스보다 구조체를 사용할 때,

C#에서 structsvalue types 이고 클래스는 reference types 입니다. enum 키워드와 struct 키워드를 사용하여 C#에서 값 형식을 만들 수 있습니다. reference type 형식 대신 value type 사용하면 관리되는 힙의 개체 수가 줄어들어 가비지 수집기(GC)의 부하가 줄어들고 GC 주기가 줄어들며 결과적으로 성능이 향상됩니다. 그러나 value types 에도 단점이 있습니다. 큰 struct 전달하는 것은 참조를 전달하는 것보다 확실히 비용이 많이 듭니다. 이는 명백한 문제 중 하나입니다. boxing/unboxing 과 관련된 오버헤드입니다. boxing/unboxing unboxing boxing 과 unboxing에 대한 좋은 설명을 보실 수 있습니다. 성능과는 별개로 값 의미 체계를 갖기 위해 형식이 필요한 경우가 있는데, 이는 reference types 만 있으면 구현하기가 매우 어렵거나 추악할 수 있습니다. 일반적으로 이러한 유형의 arrays 에서 의미론 복사가 필요하거나 자동 초기화가 필요한 경우 value types


Sujit

구조체 는 값 유형입니다. 새 변수에 구조체를 할당하면 새 변수에는 원본의 복사본이 포함됩니다.

 public struct IntStruct { public int Value {get; set;} }

다음을 실행하면 메모리에 저장된 구조체의 인스턴스 5개가 생성됩니다.

 var struct1 = new IntStruct() { Value = 0 }; // original var struct2 = struct1; // A copy is made var struct3 = struct2; // A copy is made var struct4 = struct3; // A copy is made var struct5 = struct4; // A copy is made // NOTE: A "copy" will occur when you pass a struct into a method parameter. // To avoid the "copy", use the ref keyword. // Although structs are designed to use less system resources // than classes. If used incorrectly, they could use significantly more.

클래스 는 참조 유형입니다. 클래스를 새 변수에 할당하면 변수에 원래 클래스 개체에 대한 참조가 포함됩니다.

 public class IntClass { public int Value {get; set;} }

다음을 실행하면 메모리에 클래스 개체의 인스턴스가 하나만 생성됩니다.

 var class1 = new IntClass() { Value = 0 }; var class2 = class1; // A reference is made to class1 var class3 = class2; // A reference is made to class1 var class4 = class3; // A reference is made to class1 var class5 = class4; // A reference is made to class1

구조체 는 코드 실수의 가능성을 높일 수 있습니다. 값 개체가 변경 가능한 참조 개체로 취급되는 경우 변경 내용이 예기치 않게 손실되면 개발자가 놀랄 수 있습니다.

 var struct1 = new IntStruct() { Value = 0 }; var struct2 = struct1; struct2.Value = 1; // At this point, a developer may be surprised when // struct1.Value is 0 and not 1

Jason Williams

C# 또는 기타 .net 언어의 구조 유형은 일반적으로 고정된 크기의 값 그룹처럼 동작해야 하는 항목을 보유하는 데 사용됩니다. 구조 유형의 유용한 측면은 구조 유형 인스턴스의 필드가 보관 위치를 수정하여 수정할 수 있으며 다른 방법은 없다는 것입니다. 필드를 변경하는 유일한 방법은 완전히 새로운 인스턴스를 생성한 다음 구조체 할당을 사용하여 대상의 모든 필드를 새 인스턴스의 값으로 덮어써서 변경하는 것과 같은 방식으로 구조를 코딩하는 것이 가능합니다. 구조체가 해당 필드에 기본값이 아닌 값이 있는 인스턴스를 생성하는 수단을 제공하지 않는 한 구조체 자체가 변경 가능한 위치에 저장되어 있으면 모든 필드가 변경 가능합니다.

구조에 개인 클래스 유형 필드가 포함되어 있고 자체 멤버를 래핑된 클래스 개체의 멤버로 리디렉션하는 경우 기본적으로 클래스 유형처럼 작동하도록 구조 유형을 설계할 수 있습니다. 예를 들어 PersonCollection SortedByNameSortedById 제공할 수 있습니다 PersonCollection 대한 "불변" 참조(생성자에 설정됨)를 보유하고 creator.GetNameSortedEnumerator 또는 creator.GetIdSortedEnumerator 호출하여 GetEnumerator 를 구현합니다. GetEnumerator PersonCollection 다른 메서드에 바인딩된다는 점을 제외 PersonCollection 대한 참조와 매우 유사하게 작동합니다. 하나는 구조 배열의 일부분 (포장 할 수 등 정의 할 수있는 하나 ArrayRange<T> 홀드 것이다 구조 T[] 이라 Arr , int로 Offset 하고있는 int Length 인덱싱 된 속성로하는 대하는 0에서 Length-1 의 인덱스 idx Arr[idx+Offset] 액세스합니다. 불행히도 foo 가 이러한 구조의 읽기 전용 인스턴스인 경우 현재 컴파일러 버전에서는 foo[3]+=4; foo 필드에 쓰기를 시도하는지 여부를 결정할 방법이 없기 때문입니다.

가변 크기 컬렉션을 보유하는 값 유형처럼 작동하도록 구조를 디자인하는 것도 가능하지만(구조체가 있을 때마다 복사되는 것으로 나타남) 해당 작업을 수행하는 유일한 방법은 struct는 참조를 변경할 수 있는 모든 것에 노출될 것입니다. 예를 들어, 개인 배열을 보유하는 배열과 유사한 구조를 가질 수 있으며, 인덱스된 "put" 메서드는 변경된 요소 하나를 제외하고 내용이 원본과 유사한 새 배열을 생성합니다. 불행히도 이러한 구조체를 효율적으로 수행하는 것은 다소 어려울 수 있습니다. 구조체 의미 체계가 편리할 수 있는 경우가 있지만(예: 배열과 유사한 컬렉션을 루틴에 전달할 수 있고 호출자와 호출 수신자 모두 외부 코드가 컬렉션을 수정하지 않는다는 것을 알고 있으면 호출자와 호출자를 모두 요구하는 것보다 나을 수 있습니다. 피호출자가 주어진 데이터를 방어적으로 복사하기 위해), 클래스 참조가 절대 변경되지 않을 객체를 가리켜야 한다는 요구 사항은 종종 매우 심각한 제약 조건입니다.


supercat

오해 #1: 구조체는 가벼운 클래스이다

이 신화는 다양한 형태로 나타납니다. 어떤 사람들은 값 형식이 메서드나 기타 중요한 동작을 가질 수 없거나 없어선 안 된다고 생각합니다. 값 형식은 공개 필드나 간단한 속성만 있는 간단한 데이터 전송 형식으로 사용해야 합니다. DateTime 유형은 이에 대한 좋은 반례입니다. 숫자나 문자와 같은 기본 단위라는 관점에서 값 유형이 되는 것이 합리적이며, 그 가치. 다른 방향에서 보면 데이터 전송 유형은 어쨌든 참조 유형이어야 하는 경우가 많습니다. 결정은 유형의 단순성이 아니라 원하는 값 또는 참조 유형 의미를 기반으로 해야 합니다. 다른 사람들은 값 유형이 성능 측면에서 참조 유형보다 "가벼울" 것이라고 생각합니다. 진실은 어떤 경우에는 값 유형이 더 성능이 좋다는 것입니다. 예를 들어, 값 유형은 박스형이 아니면 가비지 수집이 필요하지 않고 유형 식별 오버헤드가 없으며 역참조가 필요하지 않습니다. 그러나 다른 면에서는 참조 유형이 더 성능이 좋습니다. 매개변수 전달, 변수에 값 할당, 값 반환 및 유사한 작업을 수행하려면 4바이트 또는 8바이트만 복사하면 됩니다(32비트 또는 64비트 CLR을 실행하는지 여부에 따라 다름). ) 모든 데이터를 복사하는 대신. ArrayList가 어떻게든 "순수한" 값 유형이고 모든 데이터를 복사하는 것과 관련된 메서드에 ArrayList 표현식을 전달한다고 상상해 보십시오! 거의 모든 경우에 성능은 실제로 이러한 종류의 결정에 의해 결정되지 않습니다. 병목 현상은 예상되는 위치에 거의 없으며 성능을 기반으로 설계 결정을 내리기 전에 다양한 옵션을 측정해야 합니다. 두 믿음의 조합도 효과가 없다는 점은 주목할 가치가 있습니다. 유형이 얼마나 많은 메소드를 가지고 있는지는 중요하지 않습니다(클래스이든 구조체이든). 인스턴스당 사용되는 메모리는 영향을 받지 않습니다. (코드 자체를 위해 차지하는 메모리 측면에서 비용이 있지만, 이는 각 인스턴스가 아닌 한 번 발생합니다.)

오해 #2: 참조 유형은 힙에 존재합니다. 스택에 있는 가치 유형

이것은 종종 반복하는 사람의 게으름으로 인해 발생합니다. 첫 번째 부분은 정확합니다. 참조 유형의 인스턴스는 항상 힙에 생성됩니다. 문제를 일으키는 두 번째 부분입니다. 이미 언급했듯이 변수의 값은 선언된 모든 위치에 있습니다. 따라서 int 유형의 인스턴스 변수가 있는 클래스가 있는 경우 지정된 개체에 대한 해당 변수의 값은 항상 개체에 대한 나머지 데이터가 있는 위치에 있습니다. 힙에. 로컬 변수(메소드 내에서 선언된 변수)와 메서드 매개변수만 스택에 있습니다. C# 2 이상에서는 일부 지역 변수도 실제로 스택에 존재하지 않습니다. 5장에서 익명 메서드를 보면 알 수 있습니다. 이러한 개념이 지금 관련이 있습니까? 관리 코드를 작성하는 경우 런타임이 메모리를 가장 잘 사용하는 방법에 대해 걱정하도록 해야 한다는 것은 논쟁의 여지가 있습니다. 실제로 언어 사양은 무엇이 어디에 사는지 보장하지 않습니다. 미래 런타임은 스택에서 벗어날 수 있다는 것을 알고 있는 경우 스택에 일부 개체를 생성할 수 있거나 C# 컴파일러가 스택을 거의 사용하지 않는 코드를 생성할 수 있습니다. 다음 신화는 일반적으로 단지 용어 문제입니다.

미신 #3: 객체는 기본적으로 C#에서 참조로 전달됩니다.

이것은 아마도 가장 널리 퍼진 신화일 것입니다. 다시 말하지만, 이러한 주장을 하는 사람들은 종종(항상 그런 것은 아니지만) C#이 실제로 어떻게 작동하는지 알고 있지만 "참조에 의한 전달"이 실제로 무엇을 의미하는지 모릅니다. 불행히도 이것은 그것이 무엇을 의미하는지 아는 사람들에게는 혼란스럽습니다. 참조에 의한 전달의 공식적인 정의는 l-값 및 유사한 컴퓨터 과학 용어를 포함하여 비교적 복잡하지만 중요한 것은 참조로 변수를 전달하는 경우 호출하는 메서드가 호출자의 변수 값을 변경할 수 있다는 것입니다. 매개변수 값을 변경하여 이제 참조 유형 변수의 값은 개체 자체가 아니라 참조라는 것을 기억하십시오. 매개변수 자체를 참조로 전달하지 않고도 매개변수가 참조하는 객체의 내용을 변경할 수 있습니다. 예를 들어 다음 메서드는 해당 StringBuilder 개체의 내용을 변경하지만 호출자의 표현식은 여전히 이전과 동일한 개체를 참조합니다.

 void AppendHello(StringBuilder builder) { builder.Append("hello"); }

이 메서드가 호출되면 매개변수 값(StringBuilder에 대한 참조)이 값으로 전달됩니다. 예를 들어 builder = null; 문을 사용하여 메서드 내에서 builder 변수의 값을 변경하는 경우 해당 변경 사항은 신화와 달리 호출자에게 표시되지 않습니다. 신화의 "참조에 의한" 비트가 부정확할 뿐만 아니라 "객체가 전달됨" 비트도 정확하지 않다는 점에 주목하는 것이 흥미로웠습니다. 개체 자체는 참조나 값으로 전달되지 않습니다. 참조 유형이 관련되면 변수가 참조로 전달되거나 인수(참조)의 값이 값으로 전달됩니다. 다른 것을 제외하고 이것은 null이 by-value 인수로 사용될 때 어떤 일이 발생하는지에 대한 질문에 대한 답변입니다. 객체가 전달되는 경우 전달할 객체가 없기 때문에 문제가 발생할 수 있습니다! 대신 null 참조는 다른 참조와 동일한 방식으로 값으로 전달됩니다. 이 빠른 설명이 당신을 어리둥절하게 만들었다면 제 기사인 "C#에서 전달되는 매개변수"( http://mng.bz/otVt )에서 훨씬 더 자세히 설명합니다. 이러한 신화는 주변에 유일한 것이 아닙니다. boxing과 unboxing은 오해의 공정한 몫을 위해 왔으며, 다음에 정리하려고 합니다.

참조: Jon Skeet의 C# in Depth 3rd Edition


habib

아니 - 나는 규칙에 완전히 동의하지 않습니다. 성능 및 표준화와 관련하여 고려해야 할 좋은 지침이지만 가능성에 비추어서는 그렇지 않습니다.

응답에서 볼 수 있듯이 이를 창의적으로 사용할 수 있는 방법이 많이 있습니다. 따라서 이러한 지침은 항상 성능과 효율성을 위해 그대로 유지되어야 합니다.

이 경우 클래스를 사용하여 실제 개체를 더 큰 형태로 나타내고 구조체를 사용하여 더 정확한 용도를 가진 더 작은 개체를 나타냅니다. 당신이 말한 방식으로, "더 응집력 있는 전체". 응집력이 있는 키워드. 클래스는 더 객체 지향적인 요소가 될 것이며 구조체는 소규모이지만 이러한 특성 중 일부를 가질 수 있습니다. IMO.

일반적인 정적 속성에 매우 빠르게 액세스할 수 있는 Treeview 및 Listview 태그에서 많이 사용합니다. 나는 항상 다른 방법으로 이 정보를 얻으려고 애썼다. 예를 들어 데이터베이스 응용 프로그램에서 테이블, SP, 함수 또는 기타 개체가 있는 Treeview를 사용합니다. 구조체를 만들고 채우고 태그에 넣고 꺼내고 선택 항목의 데이터를 가져오는 등의 작업을 수행합니다. 나는 이것을 수업과 함께하지 않을 것입니다!

나는 그것들을 작게 유지하려고 노력하고 단일 인스턴스 상황에서 사용하고 변경되지 않도록 합니다. 메모리, 할당 및 성능을 알고 있는 것이 좋습니다. 그리고 테스트가 너무 필요합니다.


SnapJag

내 규칙은

1, 항상 클래스를 사용하십시오.

2, 성능 문제가 있는 경우 @IAbstract가 언급한 규칙에 따라 일부 클래스를 구조체로 변경한 다음 이러한 변경이 성능을 향상시킬 수 있는지 테스트를 수행합니다.


rockXrock

클래스는 참조 유형입니다. 클래스의 객체가 생성될 때 객체가 할당된 변수는 해당 메모리에 대한 참조만 보유합니다. 개체 참조가 새 변수에 할당되면 새 변수는 원래 개체를 참조합니다. 한 변수를 통해 수행된 변경 사항은 둘 다 동일한 데이터를 참조하기 때문에 다른 변수에 반영됩니다. 구조체는 값 유형입니다. 구조체가 생성될 때 구조체가 할당된 변수는 구조체의 실제 데이터를 보유합니다. 구조체가 새 변수에 할당되면 복사됩니다. 따라서 새 변수와 원래 변수에는 동일한 데이터의 두 개의 개별 복사본이 포함됩니다. 한 복사본에 대한 변경 사항은 다른 복사본에 영향을 주지 않습니다. 일반적으로 클래스는 더 복잡한 동작이나 클래스 개체가 생성된 후 수정하려는 데이터를 모델링하는 데 사용됩니다. 구조체는 구조체가 생성된 후 수정할 의도가 없는 데이터를 주로 포함하는 작은 데이터 구조에 가장 적합합니다.

클래스 및 구조체(C# 프로그래밍 가이드)


J_hajian_nzd

저는 WCF(Windows Communication Foundation) Named Pipe를 처리하고 있었고 데이터 교환이 참조 유형 대신 값 유형 이 되도록 하기 위해 Structs를 사용하는 것이 합리적이라는 것을 알았습니다.


N_E

C# 구조체는 클래스에 대한 경량 대안입니다. 클래스와 거의 동일한 작업을 수행할 수 있지만 클래스보다 구조체를 사용하는 것이 "비용이 덜 듭니다". 그 이유는 약간 기술적이지만 요약하면 클래스의 새 인스턴스가 힙에 배치되고 새로 인스턴스화된 구조체가 스택에 배치됩니다. 또한 클래스와 같이 구조체에 대한 참조를 다루지 않고 대신 구조체 인스턴스로 직접 작업합니다. 이것은 또한 구조체를 함수에 전달할 때 참조가 아니라 값에 의한 것임을 의미합니다. 함수 매개변수에 대한 장에서 이에 대해 더 자세히 설명합니다.

따라서 보다 단순한 데이터 구조를 표현하고 싶을 때, 특히 많은 데이터 구조를 인스턴스화할 것이라는 사실을 알고 있는 경우에는 구조체를 사용해야 합니다. .NET 프레임워크에는 Microsoft가 Point, Rectangle 및 Color 구조체와 같이 클래스 대신 구조체를 사용하는 예제가 많이 있습니다.


Saeed Dini

간단히 말해서 다음과 같은 경우 구조체를 사용하십시오.

  1. 개체 속성/필드는 변경할 필요가 없습니다. 내 말은 당신이 그들에게 초기 값을 주고 그것을 읽고 싶다는 뜻입니다.

  2. 개체의 속성과 필드는 값 유형이며 그렇게 크지 않습니다.

이 경우 스택과 힙(클래스에서)이 아닌 스택만 사용하므로 더 나은 성능과 최적화된 메모리 할당을 위해 구조체를 활용할 수 있습니다.


akazemis

좋은 첫 번째 근사치는 "절대"라고 생각합니다.

나는 좋은 두 번째 근사치가 "절대"라고 생각합니다.

성능에 대해 필사적이라면 고려하되 항상 측정하십시오.


Brian

Struct는 가비지 수집 성능을 향상시키는 데 사용할 수 있습니다. 일반적으로 GC 성능에 대해 걱정할 필요가 없지만 킬러가 될 수 있는 시나리오가 있습니다. 대기 시간이 짧은 애플리케이션의 대용량 캐시와 같습니다. 예를 보려면 이 게시물을 참조하세요.

http://00sharp.wordpress.com/2013/07/03/a-case-for-the-struct/


Rabbit

나는 구조체를 거의 사용하지 않습니다. 하지만 그건 나뿐이야. 객체가 nullable이 필요한지 여부에 따라 다릅니다.

다른 답변에서 언급했듯이 실제 객체에 클래스를 사용합니다. 나는 또한 소량의 데이터를 저장하는 데 사용되는 구조체의 사고 방식을 가지고 있습니다.




다음은 Microsoft 웹 사이트에 정의된 규칙입니다.

✔️ 유형의 인스턴스가 작고 일반적으로 수명이 짧거나 일반적으로 다른 개체에 포함되는 경우 클래스 대신 구조체를 정의하는 것을 고려하십시오.

❌ 유형에 다음 특성이 모두 없는 경우 구조체를 정의하지 마십시오.

논리적으로 기본 유형(int, double 등)과 유사한 단일 값을 나타냅니다.

인스턴스 크기는 16바이트 미만입니다.

그것은 불변입니다.

자주 상자에 넣을 필요는 없습니다.

추가 읽기를 위해


Rupesh

일반적으로 언급되는 성능 차이 외에 다른 측면을 추가하겠습니다. 바로 기본값의 사용을 공개하려는 의도입니다.

해당 필드의 기본값이 모델링된 개념의 합리적인 기본값을 나타내지 않는 경우 구조체를 사용하지 마십시오.

예.

  • 색상 또는 점은 모든 필드가 기본값으로 설정되어 있어도 의미가 있습니다. RGB 0,0,0은 완벽하게 좋은 색상이며 2D의 점으로서 (0,0)도 마찬가지입니다.
  • 그러나 Address나 PersonName에는 합리적인 기본값이 없습니다. FirstName=null 및 LastName=null인 PersonName을 이해할 수 있습니까?

클래스로 개념을 구현하면 특정 불변성을 적용할 수 있습니다. 사람은 이름과 성이 있어야 합니다. 그러나 구조체를 사용하면 모든 필드가 기본값으로 설정된 인스턴스를 생성할 수 있습니다.

따라서 합리적인 기본값이 없는 개념을 모델링할 때 클래스를 선호합니다. 클래스의 사용자는 null이 PersonName이 지정되지 않았음을 의미하지만 모든 속성이 null로 설정된 PersonName 구조체 인스턴스를 전달하면 혼란스러워할 것이라는 점을 이해할 것입니다.

(일반적인 면책 조항: 성능 고려 사항이 이 조언을 무시할 수 있습니다. 성능 문제가 있는 경우 솔루션을 결정하기 전에 항상 측정하십시오. BenchmarkDotNet 을 사용해 보세요. 정말 좋습니다!)


Vizu

✔️ 구조체 사용 고려

  1. 객체를 생성하거나 객체를 생성할 필요가 없습니다(값을 직접 할당할 수 있으며 객체 생성)
  2. 속도 또는 성능 개선 필요
  3. 생성자 및 소멸자 필요 없음(정적 계약자 사용 가능)
  4. 클래스 상속이 필요하지 않지만 인터페이스는 허용됩니다.
  5. 적은 작업량 개체 작업, 높으면 메모리 문제가 발생합니다.
  6. 변수의 기본값은 사용할 수 없습니다.
  7. 또한 사용 가능한 메서드, 이벤트, 정적 생성자, 변수 등을 구조화합니다.
  8. GC의 워크로드 감소
  9. 참조 유형이 필요없고 값 유형만(새 객체를 생성할 때마다)
  10. 불변 객체 없음(문자열은 원본을 변경하지 않고 새 문자열을 매번 반환하기 때문에 문자열은 불변 객체임)

logeshpalani31

구조는 대부분의 면에서 클래스/객체와 같습니다. 구조는 함수, 멤버를 포함할 수 있으며 상속될 수 있습니다. 그러나 구조는 C#에서 데이터 보유 용으로만 사용됩니다. 구조는 클래스 보다 RAM을 덜 사용 하고 가비지 수집기가 수집하기 더 쉽습니다 . 그러나 구조에서 함수를 사용할 때 컴파일러는 실제로 해당 구조를 클래스/객체와 매우 유사하게 사용하므로 함수와 함께 무언가를 원한다면 class/object 를 사용하십시오 .


Cryo Erger

출처 : http:www.stackoverflow.com/questions/521298/when-should-i-use-a-struct-rather-than-a-class-in-c

반응형