C #에서 클래스가 아닌 struct를 언제 사용해야합니까? 내 개념적 모델은 구조체가 항목이 단순히 값 유형의 모음 일 때 사용되는 것입니다 . 논리적으로 그것들을 모두 하나로 묶는 방법.
나는이 규칙을 보았습니다 .
- 구조체는 단일 값을 나타내야합니다.
- 구조체는 16 바이트 미만의 메모리 풋 프린트를 가져야합니다.
- 생성 후 구조체를 변경해서는 안됩니다.
이 규칙들이 효과가 있습니까? 구조체는 의미 적으로 무엇을 의미합니까?
답변
OP가 참조하는 소스에는 약간의 신뢰성이 있지만 Microsoft는 어떻습니까? 구조 사용에 대한 입장은 무엇입니까? Microsoft에서 추가 학습을 원했고 여기에 내가 찾은 것이 있습니다.
유형의 인스턴스가 작고 일반적으로 수명이 짧거나 다른 객체에 일반적으로 포함되는 경우 클래스 대신 구조를 정의하는 것이 좋습니다.
유형에 다음 특성이 모두없는 한 구조를 정의하지 마십시오.
- 기본 유형 (정수, 이중 등)과 유사한 단일 값을 논리적으로 나타냅니다.
- 인스턴스 크기가 16 바이트보다 작습니다.
- 불변입니다.
- 자주 박스에 넣을 필요는 없습니다.
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가 이러한 구조체를 사용하는 이유를 살펴 보겠습니다.
- 각각의 구조체,
Entry
그리고Enumerator
단일 값을 나타냅니다. - 속도
Entry
Dictionary 클래스 외부의 매개 변수로 전달되지 않습니다. 추가 조사에 따르면 IEnumerable의 구현을 만족시키기 위해 Dictionary는Enumerator
열거자를 요청할 때마다 복사 하는 구조체를 사용합니다 …- Dictionary 클래스의 내부.
Enumerator
Dictionary는 열거 가능하고 IEnumerator 인터페이스 구현 (예 : IEnumerator getter)에 대해 동등한 액세스 가능성을 가져야하기 때문에 공개됩니다.
업데이트 -또한 구조체가 열거자를 수행하는 것처럼 인터페이스를 구현하고 구현 된 형식으로 캐스팅하면 구조체가 참조 형식이되어 힙으로 이동합니다. Dictionary 클래스의 내부에서 Enumerator 는 여전히 값 유형입니다. 그러나 메소드가를 호출하자마자 GetEnumerator()
참조 유형 IEnumerator
이 반환됩니다.
여기서 볼 수없는 것은 구조체를 불변으로 유지하거나 인스턴스 크기를 16 바이트 이하로 유지하려는 요구 사항이나 시도입니다.
- 위의 구조체에 아무것도 선언 되지 않았습니다
readonly
– 불변 - 이 구조체의 크기는 16 바이트를 넘을 수 있습니다
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을 위반하지 않는 한 구조체는 효율적으로 기능을 수행해야합니다.
- 규칙 # 1을 충족시키기 위해 파괴가 요구되지 않는 한, 구조는 사용 중에 손상되지 않아야합니다.
… 우리는 이것에서 무엇을 제거합니까? 간단히 말해서, 값 유형의 사용에 책임이 있습니다. 빠르고 효율적이지만 제대로 유지 관리되지 않으면 예기치 않은 많은 동작을 유발할 수 있습니다 (예 : 의도하지 않은 복사본).
답변
언제든지 너를:
- 다형성이 필요하지 않습니다.
- 가치 의미론을 원하고
- 힙 할당 및 관련 가비지 콜렉션 오버 헤드를 피하려고합니다.
그러나주의해야 할 점은 구조체 (임의로 큰)가 클래스 참조 (일반적으로 하나의 기계어)보다 전달하는 데 비용이 많이 들기 때문에 실제로 클래스가 더 빠를 수 있다는 것입니다.
답변
원래 게시물에 제공된 규칙에 동의하지 않습니다. 내 규칙은 다음과 같습니다.
1) 배열에 저장할 때 성능을 위해 구조체를 사용합니다. (또한 구조체 는 언제 대답합니까? )
2) 구조화 된 데이터를 C / C ++로 /에서 전달하는 코드에 필요합니다.
3) 구조체가 필요하지 않으면 구조체를 사용하지 마십시오.
- 할당시 또는 인수로 전달할 때 “정상 객체”( 참조 유형 )와 다르게 동작하여 예기치 않은 동작이 발생할 수 있습니다. 코드를보고있는 사람이 구조체를 다루고 있다는 것을 모르는 경우 이는 특히 위험합니다.
- 상속 될 수 없습니다.
- 구조체를 인수로 전달하는 것은 클래스보다 비쌉니다.
답변
참조 의미론과 달리 값 의미론을 원할 때 구조체를 사용하십시오.
편집하다
왜 사람들이 이것을 내리고 있는지 확실하지 않지만 이것은 유효한 요점이며, op가 그의 질문을 명확하게하기 전에 이루어졌으며, 이것이 구조체의 가장 기본적인 기본 이유입니다.
참조 의미가 필요한 경우 구조체가 아닌 클래스가 필요합니다.
답변
은 “이 값은”대답 또한, 구조체를 사용하기위한 하나 개의 특정 시나리오는 당신이 언제 알고 당신이 쓰레기 수거 문제의 원인이되는 데이터의 집합을 가지고, 당신은 객체가 많이 있습니다. 예를 들어 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]);
다시 말하지만, 이것은 제자리에 있습니다. 값을 복사하지 않았습니다.
매우 구체적인 시나리오에서이 전략은 매우 성공적 일 수 있습니다. 그러나, 당신이 무엇을하고 있는지, 왜 그런지 아는 경우에만 시도해야하는 상당히 진보 된 시나리오입니다. 여기의 기본값은 클래스입니다.
답변
로부터 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 매개 변수를 제외하고 여러 상황에서 사용을 배제하는 구조체에 대한 참조를 만들 수 없습니다.
답변
구조는 데이터의 원자 적 표현에 적합하며, 상기 데이터는 코드에 의해 여러 번 복사 될 수 있습니다. 객체를 복제하는 것은 일반적으로 구조체를 복사하는 것보다 비용이 많이 듭니다. 메모리 할당, 생성자 실행 및 객체 할당 / 가비지 수집이 포함되어 있기 때문입니다.
