C #에서 클래스가 아닌 구조체를 언제 사용해야합니까?


1390

C #에서 클래스가 아닌 struct를 언제 사용해야합니까? 내 개념적 모델은 구조체가 항목이 단순히 값 유형의 모음 일 때 사용되는 것입니다 . 논리적으로 그것들을 모두 하나로 묶는 방법.

나는이 규칙을 보았습니다 .

  • 구조체는 단일 값을 나타내야합니다.
  • 구조체는 16 바이트 미만의 메모리 풋 프린트를 가져야합니다.
  • 생성 후 구조체를 변경해서는 안됩니다.

이 규칙들이 효과가 있습니까? 구조체는 의미 적으로 무엇을 의미합니까?


247
System.Drawing.Rectangle이 세 가지 규칙을 모두 위반합니다.
ChrisW

4
C #으로 작성된 꽤 많은 상용 게임이 있습니다, 요점은 최적화 된 코드를 사용하는 것입니다
BlackTigerX

25
그룹화하려는 작은 값 유형 모음이 있으면 구조가 더 나은 성능을 제공합니다. 이것은 게임 프로그래밍에서 항상 발생합니다. 예를 들어, 3D 모델의 꼭짓점은 위치, 텍스처 좌표 및 법선을 가지며 일반적으로 불변입니다. 단일 모델에는 2 천 개의 정점이 있거나 수십 개의 모델이있을 수 있지만 구조체는이 사용 시나리오에서 전체적으로 오버 헤드가 적습니다. 내 엔진 디자인을 통해 이것을 확인했습니다.
Chris D.


4
@ChrisW 알지만 그 값이 사각형을 나타내지 않습니까? "단일"값입니까? Vector3D 또는 Color와 마찬가지로 내부에도 여러 값이 있지만 단일 값을 나타내는 것 같습니다.
Marson Mao

답변:


603

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. 각각의 구조체, Entry그리고 Enumerator단일 값을 나타냅니다.
  2. 속도
  3. EntryDictionary 클래스 외부의 매개 변수로 전달되지 않습니다. 추가 조사에 따르면 IEnumerable의 구현을 만족시키기 위해 Dictionary는 Enumerator열거자를 요청할 때마다 복사 하는 구조체를 사용합니다 ...
  4. Dictionary 클래스의 내부. EnumeratorDictionary는 열거 가능하고 IEnumerator 인터페이스 구현 (예 : IEnumerator getter)에 대해 동등한 액세스 가능성을 가져야하기 때문에 공개됩니다.

업데이트 -또한 구조체가 열거자를 수행하는 것처럼 인터페이스를 구현하고 구현 된 형식으로 캐스팅하면 구조체가 참조 형식이되어 힙으로 이동합니다. Dictionary 클래스의 내부에서 Enumerator 여전히 값 유형입니다. 그러나 메소드가를 호출하자마자 GetEnumerator()참조 유형 IEnumerator이 반환됩니다.

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

  1. 위의 구조체에 아무것도 선언 되지 않았습니다readonly - 불변
  2. 이 구조체의 크기는 16 바이트를 넘을 수 있습니다
  3. Entry부정형의 수명을 갖는 (발 Add()Remove(), Clear()또는 가비지 수집);

그리고 ... 4. 두 구조체 모두 TKey와 TValue를 저장합니다. 우리 모두가 참조 유형이 될 수 있음을 알고 있습니다 (추가 보너스 정보)

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

용량 : 312874
Mem
크기 : 2660827 바이트 완료 크기 조정 : 5ms
총 충전 시간 : 889ms

용량 : 내부 어레이의 크기를 조정하기 전에 사용 가능한 요소 수.

MemSize : 사전을 MemoryStream으로 직렬화하고 바이트 길이를 얻음으로써 결정됩니다 (우리의 목적에 충분히 정확함).

크기 조정 완료 : 내부 어레이의 크기를 150862 요소에서 312874 요소로 조정하는 데 걸리는 시간. 를 통해 각 요소가 순차적으로 복사 Array.CopyTo()된다고 생각하면 너무 초라하지 않습니다.

채울 총 시간 : 로깅 및 OnResize소스에 추가 한 이벤트 로 인해 왜곡되어 있음 ; 그러나 작업 중에 15 배 크기를 조정하면서 300k 정수를 채우는 것이 여전히 인상적입니다. 호기심만으로도 이미 용량을 알고 있다면 총 충전 시간은 얼마입니까? 13ms

그렇다면 이제 Entry수업은 어떻습니까? 이 시간이나 측정 항목이 그렇게 많이 다른가요?

용량 : 312874
Mem
크기 : 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의 3 가지 로봇 법칙 에서 빌릴 것입니다 .

  1. 구조체는 사용하기에 안전해야합니다
  2. 규칙 # 1을 위반하지 않는 한 구조체는 효율적으로 기능을 수행해야합니다.
  3. 규칙 # 1을 충족시키기 위해 파괴가 요구되지 않는 한, 구조는 사용 중에 손상되지 않아야합니다.

... 우리는 이것에서 무엇을 제거합니까? 간단히 말해서, 값 유형의 사용에 책임이 있습니다. 빠르고 효율적이지만 제대로 유지 관리되지 않으면 예기치 않은 많은 동작을 유발할 수 있습니다 (예 : 의도하지 않은 복사본).


8
마이크로 소프트의 규칙에 관해서는, 불변성에 대한 규칙은 부분적 으로 변경 가능한 값 의미론이 유용 할 수 있음에도 불구하고 그들의 행동이 참조 유형과는 다른 방식으로 가치 유형의 사용을 권장하지 않는 것으로 보인다 . 유형을 조각 단위로 변경할 수 있으면 작업하기가 더 쉬워지고 유형의 저장 위치가 논리적으로 분리되어 있어야하는 경우 유형은 "변경 가능"구조체 여야합니다.
supercat


2
많은 Microsoft 유형이 해당 규칙을 위반한다는 사실은 해당 유형의 문제를 나타내는 것이 아니라 규칙이 모든 구조 유형에 적용되지 않아야 함을 나타냅니다. 구조가 단일 엔티티를 나타내는 경우 ( Decimal또는 포함 DateTime) 다른 세 규칙을 따르지 않으면 클래스로 대체해야합니다. 구조체에 고정 된 변수 컬렉션이 있고 각 변수에 해당 유형에 유효한 값이있을 수있는 경우 (예 : 다른 규칙을 Rectangle따라야하며 , 일부는 "단일 값"구조체에 대한 규칙과 반대 임) .
supercat

4
@IAbstract : 일부 사람들은 Dictionary이것이 내부 유형일뿐, 의미론보다 성능이 더 중요한 것으로 간주되거나 다른 변명에 근거 하여 입력 유형을 정당화합니다 . 필자의 요점은 Rectangle성능상의 이점이 "시맨틱 결함"보다 중요하지 않기 때문에 개별적으로 편집 가능한 필드로 내용을 노출시켜야 하는 것과 같은 유형 이지만, 그 유형이 의미 상 고정 된 독립된 값의 세트를 나타 내기 때문에 변경 가능한 구조체는 모두 더 성능적이고 의미 적으로 우수 합니다.
supercat

2
@ supercat : 나는 동의합니다 ... 그리고 내 대답의 요점은 '지침'이 꽤 약하고 행동에 대한 완전한 지식과 이해로 구조체를 사용해야한다는 것입니다. 변경 가능한 구조체에 대한 내 대답을 보려면 여기를 클릭하십시오. stackoverflow.com/questions/8108920/…
IAbstract

155

언제든지 너를:

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

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


1
그것은 단지 하나의 "캐비티"입니다. 또한 값 유형과 " (Guid)null(null을 참조 유형으로 캐스트해도 괜찮습니다) "의 "리프팅"을 고려해야 합니다.

1
C / C ++보다 비쌉니까? C ++에서 권장되는 방법은 값으로 객체를 전달하는 것입니다.
Ion Todirel

@IonTodirel 성능이 아니라 메모리 안전상의 이유로 그렇지 않습니까? 항상 트레이드 오프이지만 스택으로 32B를 전달하는 것은 레지스터로 4B 참조를 전달하는 것보다 항상 느립니다. 그러나 "값 / 참조"의 사용은 C #과 C ++에서 약간 다릅니다. 개체에 대한 참조를 전달할 때 참조를 전달하더라도 여전히 값으로 전달됩니다. 기본적으로 참조에 대한 참조가 아닌 참조의 값을 다시 전달). 의미론 의 가치 는 아니지만 기술적으로 "가치 별"입니다.
Luaan

@Luaan Copying은 비용의 한 측면 일뿐입니다. 포인터 / 참조로 인한 추가 간접 액세스도 액세스 당 비용입니다. 경우에 따라 구조체를 이동하여 복사 할 필요도 없습니다.
Onur 2019

@Onur : 흥미 롭습니다. 복사하지 않고 어떻게 "이동"합니까? asm "mov"명령은 실제로 "move"가 아니라고 생각했습니다. 복사합니다.
Winger Sendon

148

원래 게시물에 제공된 규칙에 동의하지 않습니다. 내 규칙은 다음과 같습니다.

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

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

3) 구조체가 필요하지 않으면 구조체를 사용하지 마십시오.

  • 할당시 또는 인수로 전달할 때 "정상 객체"( 참조 유형 )와 다르게 동작하여 예기치 않은 동작이 발생할 수 있습니다. 코드를보고있는 사람이 구조체를 다루고 있다는 것을 모르는 경우 이는 특히 위험합니다.
  • 상속 될 수 없습니다.
  • 구조체를 인수로 전달하는 것은 클래스보다 비쌉니다.

4
+1 네, # 1에 전적으로 동의합니다 (이것은 이미지 등과 같은 것들을 다룰 때 이점입니다). 그리고 그것들 "일반 객체"와 다르다는 것을 지적 하고 기존 지식을 제외하고는 이것을 아는 방법이 있습니다. 또는 유형 자체를 검사합니다. 또한 null 값을 구조체 유형으로 캐스팅 할 수 없습니다 :-) 이것은 실제로 핵심이 아닌 값 유형의 경우 '헝가리어'가 있거나 변수 선언 사이트에 필수 'struct'키워드가있는 경우가 거의 있습니다. .

@ pst : 무언가를 알아야한다는 것은 struct사실 어떻게 행동 해야하는지 알아야한다는 것이 사실 이지만 struct, 노출 된 필드 가있는 것이 있으면 모두 알아야합니다. 객체가 exposed-field-struct 유형의 속성을 노출하고 코드가 해당 구조체를 변수로 읽고 수정하는 경우 구조체가 작성되지 않는 한 또는 구조체가 작성 될 때까지 해당 동작이 속성을 읽은 객체에 영향을 미치지 않을 것으로 안전하게 예측할 수 있습니다. 뒤. 반대로, 속성이 변경 가능한 클래스 유형 인 경우 속성을 읽고 수정하면 기본 개체가 예상대로 업데이트 될 수 있지만 ...
supercat

... 아무것도 변경되지 않거나 변경하려고하지 않은 객체가 변경되거나 손상 될 수 있습니다. 의미가 "이 변수를 원하는대로 변경하십시오. 변경 사항은 사용자가 명시 적으로 저장하기 전까지는 아무 것도하지 않습니다"라는 코드를 갖는 코드를 갖는 것은 "어떤 숫자에 대해서도 공유 할 수있는 일부 개체에 대한 참조를 얻는 것"보다 더 명확합니다 다른 객체를 참조하거나 전혀 공유하지 않을 수 있습니다.이 객체를 변경하면 어떤 일이 발생할지 알기 위해이 객체에 대한 다른 참조를 가진 사람을 찾아야합니다. "
supercat

# 1로 시작하십시오. 구조체로 가득 찬 목록은 객체 참조로 가득 찬 목록보다 올바른 데이터를 L1 / L2 캐시에 더 많이 넣을 수 있습니다.
매트 스티븐슨

2
상속이 작업에 적합한 도구는 아니며, 프로파일 링없이 성능에 대해 너무 많이 추론하는 것은 나쁜 생각입니다. 먼저 구조체를 참조로 전달할 수 있습니다. 둘째, 참조 또는 값으로 전달하는 것이 중요한 성능 문제는 거의 없습니다. 마지막으로 클래스에 필요한 추가 힙 할당 및 가비지 수집을 고려하지 않습니다. 개인적으로, 나는 것들로 일반 오래된 데이터 및 클래스와 같은 구조체를 생각하는 것을 선호 당신은뿐만 아니라 구조체에 방법을 정의 할 수 있지만 일 (객체).
weberc2

88

참조 의미론과 달리 값 의미론을 원할 때 구조체를 사용하십시오.

편집하다

왜 사람들이 이것을 내리고 있는지 확실하지 않지만 이것은 유효한 요점이며, op가 그의 질문을 명확하게하기 전에 이루어졌으며, 이것이 구조체의 가장 기본적인 기본 이유입니다.

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


13
누구나 알고 있습니다. 그가 "struct is a value type"답변 이상을 찾고있는 것 같습니다.
TheSmurf

21
가장 기본적인 경우 이며이 게시물을 읽고 그것을 모르는 사람에게 언급해야합니다.
JoshBerke

3
이 답변이 사실이 아니라는 것은 아닙니다. 분명히 그렇습니다. 그것은 실제로 요점이 아닙니다.
TheSmurf

55
@ 조쉬 : 이미 모르는 사람이라면 답이 충분하지 않다고 말하는 것입니다. 왜냐하면 그것이 무엇을 의미하는지 알지 못하기 때문입니다.
TheSmurf

1
나는 다른 답변 중 하나가 맨 위에 있어야한다고 생각하기 때문에 이것을 하향 투표했습니다. "관리되지 않는 코드와의 상호 운용을 위해서는 그렇지 않으면 피하십시오"라는 답변.
Daniel Earwicker

59

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

데이터가 구조체에 대해 너무 큰 크기 일 수 있기 때문에이 데이터로 작업하는 것은 어색하며, 항상 지방 값을 복사하고 싶지는 않습니다. 그러나 배열에서 직접 액세스하면 구조체가 복사되지 않습니다. 복사하는 목록 인덱서와 대조적입니다. 이것은 인덱스 작업이 많다는 것을 의미합니다.

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

값 자체를 변경 불가능하게 유지하면 여기에 도움이됩니다. 보다 복잡한 논리의 경우 by-ref 매개 변수가있는 메소드를 사용하십시오.

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

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

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


+1 재미있는 답변. 그러한 접근 방식에 대한 실제 일화를 기꺼이 공유 하시겠습니까?
Jordão

모바일에서 @Jordao하지만 대한 Google 검색 : + gravell + "GC에 의한 공격"
마크 Gravell

1
고마워 여기 에서 찾았습니다 .
Jordão

2
@MarcGravell 왜 언급 했는가 : 배열 (목록 아님)을 사용 하는가? List나는 뒷장면을 사용한다고 생각 Array합니다. 아니 ?
Royi Namir

4
@RoyiNamir 나는 이것에 대해서도 궁금했지만, 대답은 Marc의 대답의 두 번째 단락에 있다고 생각합니다. "하지만 배열에서 직접 액세스하면 구조체를 복사 할 수 없습니다. 복사하는 목록 인덱서와 대조적으로 제 위치에 있습니다."
user1323245

40

로부터 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 인스턴스는 어레이에 인라인으로 저장됩니다.

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

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

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

Point가 클래스 인 경우 a와 b가 동일한 객체를 참조하므로 출력은 20입니다. Point가 구조체 인 경우 a ~ b를 할당하면 값의 복사본이 만들어지고 이후에 ax를 할당해도이 복사본은 영향을받지 않으므로 출력은 10입니다.

앞의 예는 구조체의 두 가지 한계를 강조합니다. 첫째, 전체 구조체를 복사하는 것이 일반적으로 객체 참조를 복사하는 것보다 비효율적이므로 할당 및 값 매개 변수 전달은 구조체에서 참조 유형보다 비용이 많이 듭니다. 둘째, ref 및 out 매개 변수를 제외하고 여러 상황에서 사용을 배제하는 구조체에 대한 참조를 만들 수 없습니다.


4
구조체에 대한 참조를 유지할 수 없다는 사실은 때로는 제한적이지만 매우 유용한 특성이기도합니다. .net의 주요 약점 중 하나는 해당 객체의 제어권을 잃지 않고 외부 코드를 변경 가능한 객체에 대한 참조를 전달할 수있는 적절한 방법이 없다는 것입니다. 대조적으로, 외부 메소드 ref를 변경 가능한 구조체에 안전하게 제공 할 수 있으며 외부 메소드가 수행 할 모든 돌연변이가 리턴되기 전에 수행 될 것임을 알 수 있습니다. 그것은 너무 나쁘다 .net은 임시 매개 변수와 함수 반환 값의 개념을 가지고 있지 않기 때문에 ...
supercat

4
...이를 통해 전달 된 구조체의 유리한 의미 ref를 클래스 객체로 달성 할 수 있습니다. 기본적으로 지역 변수, 매개 변수 및 함수 반환 값은 지속 가능 (기본값), 반환 가능 또는 임시 일 수 있습니다. 임시 범위의 물건을 현재 범위보다 오래 지속되는 것으로 복사하는 것은 금지되어 있습니다. 반환 가능한 것은 함수에서 반환 될 수 있다는 점을 제외하면 임시적인 것과 같습니다. 함수의 반환 값은 "반환 가능"매개 변수에 적용되는 가장 엄격한 제한 사항에 의해 제한됩니다.
supercat

34

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


4
예, 그러나 큰 구조체는 클래스 참조보다 비쌉니다 (메소드로 전달할 때).
Alex

27

기본 규칙은 다음과 같습니다.

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

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

추방

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
}

3
불변의 참조 유형 string은 의미 적으로 값과 동일하며 불변의 객체에 대한 참조를 필드에 저장하는 것은 힙 할당을 수반하지 않습니다. 노출 공용 필드 구조체 노출 공용 필드 클래스 오브젝트의 차이는 부호 계열 주어진 인 var q=p; p.X=4; q.X=5;, p.X경우 4 값을 갖 a은 클래스 타입인지 5 구조형이고. 유형의 멤버를 편리하게 수정하려면 변경 사항 q이 영향 을 줄지 여부에 따라 'class'또는 'struct'를 선택해야합니다 p.
supercat

예, 참조 변수가 스택에 있지만 참조하는 객체가 힙에 존재한다는 데 동의합니다. 구조체와 클래스가 다른 변수에 할당 될 때 다르게 동작하지만 강한 결정 요인이라고 생각하지 않습니다.
Usman Zafar

가변 구조체와 가변 클래스는 완전히 다르게 동작합니다. 하나가 옳다면 다른 하나는 틀렸을 것입니다. 구조체 또는 클래스를 사용할지 여부를 결정하는 데 행동이 결정 요소가 아닌지 확실하지 않습니다.
supercat

클래스 또는 구조체를 만들 때 클래스가 어떻게 사용 될지 잘 모르기 때문에 강력한 결정 요소가 아니라고 말했습니다. 따라서 디자인 관점에서 일이 더 의미있는 방식에 집중합니다. 어쨌든 구조체에 참조 변수가 포함 된 .NET 라이브러리의 단일 위치에서는 본 적이 없습니다.
Usman Zafar

1
구조 유형 은 항상 클래스 유형 인을 ArraySegment<T>캡슐화합니다 T[]. 구조 유형 KeyValuePair<TKey,TValue>은 종종 클래스 유형과 함께 일반 매개 변수로 사용됩니다.
supercat

19

첫 번째 : Interop 시나리오 또는 메모리 레이아웃을 지정해야 할 때

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


17

StructLayoutAttribute ( 일반적으로 PInvoke)를 사용하여 메모리 레이아웃을 명시 적으로 지정하려는 경우 "struct"를 사용해야합니다 .

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


5
StructLayoutAttribute는 구조체 나 클래스에 적용 할 수 있으므로 구조체를 사용해야하는 이유는 아닙니다.
Stephen Martin

관리되지 않는 메소드 호출에 인수를 전달하는 것이 왜 이치에 맞습니까?
David Klempfner

16

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

여기에 나와있는 세 가지 지침은이 맥락에서 유용하지 않았습니다. 특정 순서로 4 백 바이트의 내용을 작성해야 할 때 4 백 바이트 구조체를 정의하고 관련이없는 값으로 채울 것입니다. 당시 가장 의미있는 방식으로 설정했습니다. (좋아요, 4 백 바이트는 꽤 이상 할 것입니다. 그러나 생계를 위해 Excel 파일을 작성할 때, 최대 약 40 바이트의 구조체를 다루고있었습니다. BIFF 레코드의 크기가 얼마나 크니까요.)


그래도 참조 유형을 쉽게 사용할 수는 없습니까?
David Klempfner

15

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

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

# 2 것 같다 부분 닷넷 컬렉션 클래스에서 구조체의 유병률에 대한 이유의 ..
IAbstract

클래스 유형의 저장 위치를 ​​만들 때 가장 먼저 할 일은 해당 유형의 새 인스턴스를 만들고 해당 위치에 참조를 저장하고 다른 곳에서 참조를 복사하거나 덮어 쓰지 않는 것입니다. 클래스는 동일하게 동작합니다. Structs는 한 인스턴스에서 다른 인스턴스로 모든 필드를 복사하는 편리한 표준 방법을 가지고 있으며 클래스에 대한 참조를 절대로 복제하지 않는 경우 (일반적으로 this메서드를 호출하는 데 사용되는 임시 매개 변수 제외) 더 나은 성능을 제공합니다 . 클래스는 하나의 참조를 복제 할 수 있습니다.
supercat

13

.NET 지원 value typesreference types(자바, 당신은 단지 참조 유형을 정의 할 수 있습니다). 인스턴스는 reference types관리되는 힙에 할당되며 미해결 참조가없는 경우 가비지 수집됩니다. value types반면에의 인스턴스 는에 할당 stack되므로 할당 된 메모리는 범위가 종료되는 즉시 회수됩니다. 그리고 물론 value types가치와 reference types참조로 전달 하십시오. System.String을 제외한 모든 C # 기본 데이터 형식은 값 형식입니다.

struct over class를 사용할 때

C #에서 structsare value types는 클래스가 reference types입니다. enum키워드와 키워드를 사용하여 C #에서 값 유형을 만들 수 있습니다 struct. value type대신에를 사용 reference type하면 관리되는 힙에있는 객체 수가 줄어들어 가비지 수집기 (GC)에 대한로드가 줄어들고 GC주기가 잦아지며 결과적으로 성능이 향상됩니다. 그러나 value types단점도 있습니다. 큰 struct것을 전달하는 것은 참조를 전달하는 것보다 확실히 비용이 많이 듭니다. 그것은 명백한 문제입니다. 다른 문제는와 관련된 오버 헤드입니다 boxing/unboxing. 경우 당신은 무엇을 궁금해하는 boxing/unboxing의미에 대한 좋은 설명에 대해 다음 링크를 따라 boxingunboxing. 성능 외에도, 가치 의미론을 갖기 위해 단순히 유형이 필요한 경우 reference types가 있습니다. value types일반적 arrays으로 이러한 유형의 복사 시맨틱이 필요하거나 자동 초기화가 필요한 경우 에만 사용해야 합니다 .


작은 구조를 복사하거나 값으로 전달하는 것은 클래스 참조를 복사하거나 전달하거나으로 구조를 전달하는 것보다 저렴합니다 ref. 모든 크기 구조 ref를 전달하는 것은 값으로 클래스 참조를 전달하는 것과 같습니다. 크기 구조를 복사하거나 값으로 전달하는 것은 클래스 객체의 방어 적 복사를 수행하고 참조를 저장하거나 전달하는 것보다 저렴합니다. 큰 배 클래스는 (1) 클래스 (운전자 복사를 방지하도록,) 불변이며, 생성되는 각각의 인스턴스가 많이 주위에 전달되거나, ...의 값을 저장하기위한 구조체보다 나은
supercat

... (2) 다양한 이유로 구조체를 사용할 수 없을 때 (예 : 나무와 같은 것에 대해 중첩 참조를 사용해야하거나 다형성이 필요하기 때문에) 값 유형을 사용할 때는 일반적으로 특정 이유가없는 필드를 직접 노출해야합니다 (대부분의 클래스 유형 필드는 속성 내에 래핑되어야 함). 어떤 컴파일러는 하나가 읽기 전용 구조체에 속성 setter를 호출 할 수있는 것 중에 가변 치 형의 소위 "악"의 대부분은 예를 들어, 속성 필드 (의 불필요한 포장에서 줄기 ... 가끔 때문에
supercat

... 올바른 일을하십시오. 모든 컴파일러는 그러한 구조에 직접 필드를 설정하려는 시도를 제대로 거부합니다. 컴파일러는 거부 할 수 있도록하는 가장 좋은 방법은 readOnlyStruct.someMember = 5;만들 수 없습니다 someMember읽기 전용 속성을하지만, 대신 필드합니다.
supercat

12

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

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

12

나는 BenchmarkDotNet 으로 작은 벤치 마크 를 작성하여 숫자의 "struct"이점을 더 잘 이해했습니다. 구조체 (또는 클래스)의 배열 (또는 목록)을 통한 루핑을 테스트하고 있습니다. 이러한 배열이나 목록을 만드는 것은 벤치 마크의 범위를 벗어납니다. "클래스"가 더 무거울수록 더 많은 메모리를 사용하고 GC가 포함됩니다.

결론은 LINQ와 숨겨진 구조체 boxing / unboxing에주의하고 미세 최적화를 위해 구조체를 사용하면 배열을 엄격하게 유지하는 것입니다.

추신 : 콜 스택을 통해 구조체 / 클래스를 전달하는 또 다른 벤치 마크는 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;
        }
    }

리스트 등에서 구조체를 사용할 때 왜 구조체가 너무 느려지는지 알아 보셨습니까? 당신이 언급 한 숨겨진 권투 및 unboxing 때문입니까? 그렇다면 왜 그런 일이 발생합니까?
Marko Grdinic

추가 참조가 필요하지 않기 때문에 배열에서 구조체에 액세스하는 것이 더 빠릅니다. 복싱 / 언 박스는 linq의 경우입니다.
로마 Pokrovskij

10

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

구조에 개인 클래스 유형 필드가 포함되어 있고 자체 멤버를 랩핑 된 클래스 오브젝트의 필드로 경로 재지 정하는 경우 기본적으로 클래스 유형처럼 작동하도록 구조 유형을 설계 할 수 있습니다. 예를 들어, PersonCollection힘의 서비스 특성 SortedByNameSortedById하는에 "불변"참조 잡아 둘 PersonCollection(자신의 생성자에서 설정) 및 구현 GetEnumerator중 하나를 호출하여 creator.GetNameSortedEnumerator또는 creator.GetIdSortedEnumerator. 이러한 구조체는 PersonCollectionGetEnumerator메서드가의 다른 메서드에 바인딩 된다는 점을 제외하고는에 대한 참조와 매우 유사하게 동작 합니다 PersonCollection. 하나는 구조가 예를 들어 하나가 정의 할 수있다 (배열 부분 포장 할 수 ArrayRange<T>홀드 것이다 구조 T[]라는 Arr, int를 Offset, 및 INTLengthidx0에서 ~ 사이 의 색인 에 대해 Length-1액세스 할 수 있는 색인 된 속성이있는 Arr[idx+Offset]). 불행히도, foo그러한 구조의 읽기 전용 인스턴스 인 경우 현재 컴파일러 버전은 foo[3]+=4;이러한 연산이의 필드에 쓰려고 시도하는지 여부를 결정할 방법이 없으므로 이와 같은 연산을 허용하지 않습니다 foo.

변수 크기의 컬렉션을 보유하는 값 유형과 유사하게 동작하도록 구조체를 설계하는 것도 가능합니다 (구조가있을 때마다 복사되는 것처럼 보일 것입니다).하지만 그 작업을 수행하는 유일한 방법은 struct hold 참조는 참조를 변경시킬 수있는 모든 것에 노출됩니다. 예를 들어, 개인 배열을 보유하고 인덱스 된 "put"메소드가 하나의 변경된 요소를 제외하고 원래 배열의 내용과 동일한 새 배열을 작성하는 배열과 유사한 구조를 가질 수 있습니다. 불행히도 그러한 구조체를 효율적으로 수행하는 것은 다소 어려울 수 있습니다. 구조체 의미론이 편리 할 수있는 시간이 있지만 (예 : 발신자와 수신자 모두 외부 코드가 컬렉션을 수정하지 않는다는 것을 알고 배열과 같은 컬렉션을 루틴에 전달할 수 있음)


10

아냐-나는 규칙에 전적으로 동의하지 않는다. 그것들은 성능과 표준화로 고려해야 할 좋은 지침이지만 가능성을 고려하지는 않습니다.

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

이 경우 클래스를 사용하여 실제 객체를 더 큰 형태로 표현하고 구조체를 사용하여 더 정확한 용도의 작은 객체를 표현합니다. 당신이 말한 방식은 "보다 응집력있는 전체"입니다. 응집력있는 키워드입니다. 클래스는 더 객체 지향적 인 요소이지만 구조체는 더 작은 규모이지만 이러한 특성 중 일부를 가질 수 있습니다. IMO.

일반적인 정적 속성에 매우 빠르게 액세스 할 수있는 Treeview 및 Listview 태그에서 많이 사용합니다. 나는 항상이 정보를 다른 방법으로 얻기 위해 고군분투했다. 예를 들어, 데이터베이스 응용 프로그램에서 테이블, SP, 함수 또는 다른 개체가있는 Treeview를 사용합니다. 나는 구조체를 생성하고 채우고, 태그에 넣고, 뽑아 내고, 선택 데이터 등을 얻는다. 나는 수업으로 이것을하지 않을 것입니다!

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


구조는 경량의 불변 개체를 나타내는 데 현명하게 사용되거나 관련되어 있지만 독립적 인 변수의 고정 세트 (예 : 점의 좌표)를 나타내는 데 현명하게 사용될 수 있습니다. 이 페이지의 조언은 전자의 목적에 부합하도록 설계된 구조체에는 좋지만 후자의 목적에 적합하도록 설계된 구조체에는 적합하지 않습니다. 나의 현재 생각은 개인 필드를 가진 구조체는 일반적으로 표시된 설명을 충족해야하지만 많은 구조체는 공개 필드를 통해 전체 상태를 노출해야한다는 것입니다.
supercat

"3d 포인트"유형의 스펙이 전체 상태가 읽을 수있는 멤버 x, y 및 z를 통해 노출됨을 나타내며 double해당 좌표에 대한 값 조합을 사용하여 인스턴스를 작성할 수있는 경우 이러한 스펙은 멀티 스레드 동작에 대한 세부 사항을 제외하고는 노출 된 필드 구조체와 의미 적으로 동일하게 동작합니다 (불변 클래스는 어떤 경우에는 더 나은 반면 노출 된 필드 구조체는 다른 경우에는 더 좋을 것입니다. 소위 "불변"구조체는 모든 경우에 더 나쁘다).
supercat

8

내 규칙은

1, 항상 수업 사용;

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


Microsoft가 무시하는 실질적인 사용 사례는 유형 변수가 Foo독립적 인 값 (예 : 점 좌표)의 고정 된 컬렉션을 캡슐화하여 때로는 그룹으로 전달하고 때로는 독립적으로 변경하려는 경우입니다. 나는 두 가지 목적을 간단한 노출 필드 구조체 (독립 변수의 고정 수집, 법안에 완벽하게 부합)와 거의 잘 결합하는 클래스를 사용하는 패턴을 찾지 못했습니다.
supercat

1
@ supercat : Microsoft를 비난하는 것이 전적으로 공평하지 않다고 생각합니다. 여기서 실제 문제는 객체 지향 언어 인 C #은 많은 동작없이 데이터 만 노출하는 일반 레코드 유형에만 집중하지 않는다는 것입니다. C #은 C ++과 같은 수준의 다중 패러다임 언어가 아닙니다. 그 존재는 내가 말했다, 또한 그래서 아마도 C #을 너무 이상적인 언어입니다, 아주 소수의 사람들이 순수한 OOP 프로그램 믿는다. (내가 하나가 최근 노출 시작했다 위해 public readonly생성하기 때문에 읽기 전용 특성이 실질적으로 어떤 이익을 위해 단순히 너무 많은 일을하다, 너무, 내 유형의 필드를.)
stakx - 더 이상 기여

1
@stakx : 그러한 유형에 "초점"을 둘 필요가 없습니다. 그들이 무엇인지 알면 충분합니다. 구조체와 관련하여 C #의 가장 큰 약점은 다른 많은 영역에서도 가장 큰 문제입니다. 언어는 특정 변환이 적절한 지 또는 적절하지 않은지를 나타내는 부적절한 시설을 제공하며 이러한 시설이 없으면 불행한 디자인 결정을 내립니다. 예를 들어, "가변 구조체는 악"의 99 %는 돌려 컴파일러의 유래 MyListOfPoint[3].Offset(2,3);var temp=MyListOfPoint[3]; temp.Offset(2,3);... 적용 할 때 가짜를 인 변환
supercat

... Offset방법에. 그러한 가짜 코드를 방지하는 올바른 방법은 구조체를 불필요하게 불변으로 만들지 말고 대신에 Offset앞서 언급 한 변환을 금지하는 속성으로 태그를 지정 하는 것을 허용 해야합니다. 암시 적 숫자 변환도 호출이 분명한 경우에만 적용 할 수 있도록 태그를 지정할 수 있으면 훨씬 더 좋을 수 있습니다. foo(float,float)foo(double,double)에 대한 과부하가 float있는 double경우 a를 사용하려고 하면 종종 암시 적 변환을 적용하지 말고 대신 오류가 있어야한다고 생각합니다.
supercat

double값을에 직접 할당 float하거나 float인수를 취할 수는 있지만 메소드를 전달할 수없는 메소드에 전달 double하면 프로그래머가 의도 한대로 수행됩니다. 반대로 명시적인 타입 캐스트없이 float표현식을 할당하는 double것은 종종 실수입니다. 암시 적 double->float변환을 허용 할 수있는 유일한 시간 은 이상적이지 않은 과부하가 선택 될 때입니다. 나는 그것을 막을 수있는 올바른 방법은 implcit double-> float을 금지하지 않아야하지만 변환을 허용하지 않는 속성으로 과부하를 태깅하는 것이 아니라고 주장했다.
supercat

8

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

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


덕트 테이프와 함께 관련 있지만 독립적 인 몇 가지 변수를 고정해야하는 경우에도 구조가 매우 좋습니다 (예 : 점 좌표). MSDN 지침은 개체처럼 동작하지만 집계를 디자인 할 때 훨씬 덜 적합한 구조를 만들려고하는 경우 합리적입니다. 그중 일부는 후자의 상황에서 거의 정확하게 잘못 되었습니다. 예를 들어, 유형에 의해 캡슐화 된 변수의 독립 정도가 클수록 불변 클래스보다는 노출 필드 구조를 사용하는 이점이 커집니다.
supercat

6

오해 # 1 : 구조는 경량급입니다

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

오해 # 2 : 참조 유형이 힙에 존재 함; 가치 유형은 스택에 라이브

이것은 종종 그것을 반복하는 사람의 게으름으로 인해 발생합니다. 첫 번째 부분은 정확합니다. 참조 유형의 인스턴스는 항상 힙에 작성됩니다. 문제를 일으키는 것은 두 번째 부분입니다. 이미 언급했듯이 변수의 값은 선언 된 곳마다 유지되므로 int 유형의 인스턴스 변수가있는 클래스가있는 경우 주어진 객체에 대한 변수의 값은 항상 객체의 나머지 데이터가있는 위치입니다. 힙에. 로컬 변수 (메소드 내에 선언 된 변수) 및 메소드 매개 변수 만 스택에 있습니다. C # 2 이상에서는 5 장에서 익명 메소드를 볼 때 알 수 있듯이 일부 로컬 변수조차 실제로 스택에 존재하지 않습니다. 관리되는 코드를 작성하는 경우 런타임에 메모리가 가장 잘 사용되는 방식에 대해 걱정해야한다고 주장 할 수 있습니다. 과연, 언어 사양은 어디에 살고 있는지에 대해 보증하지 않습니다. 미래의 런타임은 스택에서 벗어날 수 있다는 것을 알면 스택에 일부 객체를 만들 수 있거나 C # 컴파일러가 스택을 거의 사용하지 않는 코드를 생성 할 수 있습니다. 다음 신화는 일반적으로 용어 문제입니다.

오해 # 3 : 개체가 C #에서 참조로 전달됨

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

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

이 메소드가 호출되면 매개 변수 값 (StringBuilder에 대한 참조)이 값으로 전달됩니다. 메소드 내에서 builder 변수의 값을 변경해야하는 경우 (예 : statement builder = null;) 신화와 달리 호출자가 해당 변경 사항을 볼 수 없습니다. 신화의 "기준으로"비트가 부정확 할뿐만 아니라 "객체가 통과되었습니다"비트도 주목할만한 점이 흥미 롭습니다. 객체 자체는 참조 또는 값으로 전달되지 않습니다. 참조 유형이 관련된 경우 변수는 참조로 전달되거나 인수 값 (참조)이 값으로 전달됩니다. 다른 것 외에도, 이것은 null이 값으로 인수로 사용될 때 발생하는 문제에 대한 대답입니다. 만약 객체가 전달되면 객체가 전달되지 않기 때문에 문제가 발생할 수 있습니다! 대신에 널 참조는 다른 참조와 같은 방식으로 값으로 전달됩니다. 이 간단한 설명으로 인해 당황 스러웠 으면 내 기사 인 "C #에서 매개 변수 전달"(http://mng.bz/otVt )를 참조하십시오. 이 신화가 유일한 것은 아닙니다. 복싱과 언 박싱은 오해의 공정한 몫을 위해 온다, 나는 다음에 정리하려고 노력할 것이다.

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


1
당신이 옳다고 가정하면 아주 좋습니다. 참조를 추가하는 것도 좋습니다.
NoChance

5

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

좋은 초 근사치는 "never"라고 생각합니다.

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


24
나는 그 대답에 동의하지 않을 것입니다. Structs는 많은 시나리오에서 합법적으로 사용됩니다. 다음은 원자 적 방식으로 데이터 크로스 프로세스를 마샬링하는 예입니다.
Franci Penov

25
귀하의 게시물을 편집하고 귀하의 요점을 정교하게 작성해야합니다. 귀하는 귀하의 의견을 제시했지만, 귀하가이 의견을 취하는 이유와 함께이를 보완해야합니다.
Erik Forbes

4
구조체를 사용 하려면 Totin 'Chip 카드 ( en.wikipedia.org/wiki/Totin%27_Chip ) 와 동등한 것이 필요하다고 생각 합니다. 진심으로.
그렉

4
87.5K 사용자는 이와 같은 답변을 어떻게 게시합니까? 어렸을 때 그렇게 했습니까?
Rohit Vipin Mathews 2016 년

3
@Rohit-6 년 전이었습니다. 당시 사이트 표준은 매우 달랐습니다. 그러나 이것은 여전히 ​​나쁜 대답입니다. 그렇습니다.
앤드류 아놀드

5

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


1
이것이 IMHO의 가장 좋은 단서입니다.
Ivan

4

C # 구조체는 클래스의 간단한 대안입니다. 클래스와 거의 동일하게 수행 할 수 있지만 클래스가 아닌 구조체를 사용하는 것이 "비싸지 않습니다". 그 이유는 약간 기술적 인 것이지만 요약하자면 클래스의 새 인스턴스가 힙에 배치되고 새로 인스턴스화 된 구조체가 스택에 배치됩니다. 또한 클래스와 같은 구조체에 대한 참조를 다루지 않고 구조체 인스턴스와 직접 작업합니다. 이는 또한 구조체에 함수를 전달할 때 참조가 아니라 값을 기준으로한다는 것을 의미합니다. 기능 매개 변수에 관한 장에서 이에 대한 자세한 내용이 있습니다.

따라서 더 간단한 데이터 구조를 나타내려면 특히 구조체를 많이 인스턴스화 할 것임을 알고있는 경우 구조체를 사용해야합니다. .NET 프레임 워크에는 Microsoft가 클래스 대신 구조체, 예를 들어 Point, Rectangle 및 Color 구조체를 사용한 예제가 많이 있습니다.


3

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

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


3

다음 시나리오에서 구조 또는 값 유형을 사용할 수 있습니다.

  1. 가비지 수집으로 개체를 수집하지 않으려는 경우.
  2. 단순 유형이고 멤버 함수가 인스턴스 필드를 수정하지 않는 경우
  3. 다른 유형에서 파생되거나 다른 유형에서 파생 될 필요가없는 경우

이 링크 에서 값 유형 및 값 유형 에 대해 자세히 알 수 있습니다.


3

간단히 다음과 같은 경우 struct를 사용하십시오.

1- 객체 속성 / 필드를 변경할 필요가 없습니다. 나는 당신이 그들에게 초기 값을주고 읽고 싶다는 것을 의미합니다.

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

이 경우 스택과 힙 (클래스)이 아닌 스택 만 사용하므로 성능을 높이고 메모리 할당을 최적화하는 구조체를 활용할 수 있습니다.


2

나는 거의 구조체를 사용하지 않습니다. 그러나 그것은 단지 나입니다. 객체가 nullable이 필요한지 여부에 달려 있습니다.

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


-11

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


2
구조는 상속 될 수 없습니다. msdn.microsoft.com/en-us/library/0taef578.aspx
HimBromBeere
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.