C #에서 클래스가 아닌 struct를 언제 사용해야합니까? 내 개념적 모델은 구조체가 항목이 단순히 값 유형의 모음 일 때 사용되는 것입니다 . 논리적으로 그것들을 모두 하나로 묶는 방법.
나는이 규칙을 보았습니다 .
- 구조체는 단일 값을 나타내야합니다.
- 구조체는 16 바이트 미만의 메모리 풋 프린트를 가져야합니다.
- 생성 후 구조체를 변경해서는 안됩니다.
이 규칙들이 효과가 있습니까? 구조체는 의미 적으로 무엇을 의미합니까?
C #에서 클래스가 아닌 struct를 언제 사용해야합니까? 내 개념적 모델은 구조체가 항목이 단순히 값 유형의 모음 일 때 사용되는 것입니다 . 논리적으로 그것들을 모두 하나로 묶는 방법.
나는이 규칙을 보았습니다 .
이 규칙들이 효과가 있습니까? 구조체는 의미 적으로 무엇을 의미합니까?
답변:
OP가 참조하는 소스에는 약간의 신뢰성이 있지만 Microsoft는 어떻습니까? 구조 사용에 대한 입장은 무엇입니까? Microsoft에서 추가 학습을 원했고 여기에 내가 찾은 것이 있습니다.
유형의 인스턴스가 작고 일반적으로 수명이 짧거나 다른 객체에 일반적으로 포함되는 경우 클래스 대신 구조를 정의하는 것이 좋습니다.
유형에 다음 특성이 모두없는 한 구조를 정의하지 마십시오.
- 기본 유형 (정수, 이중 등)과 유사한 단일 값을 논리적으로 나타냅니다.
- 인스턴스 크기가 16 바이트보다 작습니다.
- 불변입니다.
- 자주 박스에 넣을 필요는 없습니다.
어쨌든, # 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
열거자를 요청할 때마다 복사 하는 구조체를 사용합니다 ...Enumerator
Dictionary는 열거 가능하고 IEnumerator 인터페이스 구현 (예 : IEnumerator getter)에 대해 동등한 액세스 가능성을 가져야하기 때문에 공개됩니다. 업데이트 -또한 구조체가 열거자를 수행하는 것처럼 인터페이스를 구현하고 구현 된 형식으로 캐스팅하면 구조체가 참조 형식이되어 힙으로 이동합니다. Dictionary 클래스의 내부에서 Enumerator 는 여전히 값 유형입니다. 그러나 메소드가를 호출하자마자 GetEnumerator()
참조 유형 IEnumerator
이 반환됩니다.
여기서 볼 수없는 것은 구조체를 불변으로 유지하거나 인스턴스 크기를 16 바이트 이하로 유지하려는 요구 사항이나 시도입니다.
readonly
- 불변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 가지 로봇 법칙 에서 빌릴 것입니다 .
... 우리는 이것에서 무엇을 제거합니까? 간단히 말해서, 값 유형의 사용에 책임이 있습니다. 빠르고 효율적이지만 제대로 유지 관리되지 않으면 예기치 않은 많은 동작을 유발할 수 있습니다 (예 : 의도하지 않은 복사본).
Decimal
또는 포함 DateTime
) 다른 세 규칙을 따르지 않으면 클래스로 대체해야합니다. 구조체에 고정 된 변수 컬렉션이 있고 각 변수에 해당 유형에 유효한 값이있을 수있는 경우 (예 : 다른 규칙을 Rectangle
따라야하며 , 일부는 "단일 값"구조체에 대한 규칙과 반대 임) .
Dictionary
이것이 내부 유형일뿐, 의미론보다 성능이 더 중요한 것으로 간주되거나 다른 변명에 근거 하여 입력 유형을 정당화합니다 . 필자의 요점은 Rectangle
성능상의 이점이 "시맨틱 결함"보다 중요하지 않기 때문에 개별적으로 편집 가능한 필드로 내용을 노출시켜야 하는 것과 같은 유형 이지만, 그 유형이 의미 상 고정 된 독립된 값의 세트를 나타 내기 때문에 변경 가능한 구조체는 모두 더 성능적이고 의미 적으로 우수 합니다.
언제든지 너를:
그러나주의해야 할 점은 구조체 (임의로 큰)가 클래스 참조 (일반적으로 하나의 기계어)보다 전달하는 데 비용이 많이 들기 때문에 실제로 클래스가 더 빠를 수 있다는 것입니다.
원래 게시물에 제공된 규칙에 동의하지 않습니다. 내 규칙은 다음과 같습니다.
1) 배열에 저장할 때 성능을 위해 구조체를 사용합니다. (또한 구조체 는 언제 대답합니까? )
2) 구조화 된 데이터를 C / C ++로 /에서 전달하는 코드에 필요합니다.
3) 구조체가 필요하지 않으면 구조체를 사용하지 마십시오.
struct
사실 어떻게 행동 해야하는지 알아야한다는 것이 사실 이지만 struct
, 노출 된 필드 가있는 것이 있으면 모두 알아야합니다. 객체가 exposed-field-struct 유형의 속성을 노출하고 코드가 해당 구조체를 변수로 읽고 수정하는 경우 구조체가 작성되지 않는 한 또는 구조체가 작성 될 때까지 해당 동작이 속성을 읽은 객체에 영향을 미치지 않을 것으로 안전하게 예측할 수 있습니다. 뒤. 반대로, 속성이 변경 가능한 클래스 유형 인 경우 속성을 읽고 수정하면 기본 개체가 예상대로 업데이트 될 수 있지만 ...
참조 의미론과 달리 값 의미론을 원할 때 구조체를 사용하십시오.
왜 사람들이 이것을 내리고 있는지 확실하지 않지만 이것은 유효한 요점이며, 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]);
다시 말하지만, 이것은 제자리에 있습니다. 값을 복사하지 않았습니다.
매우 구체적인 시나리오에서이 전략은 매우 성공적 일 수 있습니다. 그러나, 당신이 무엇을하고 있는지, 왜 그런지 아는 경우에만 시도해야하는 상당히 진보 된 시나리오입니다. 여기의 기본값은 클래스입니다.
List
나는 뒷장면을 사용한다고 생각 Array
합니다. 아니 ?
로부터 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 매개 변수를 제외하고 여러 상황에서 사용을 배제하는 구조체에 대한 참조를 만들 수 없습니다.
ref
를 변경 가능한 구조체에 안전하게 제공 할 수 있으며 외부 메소드가 수행 할 모든 돌연변이가 리턴되기 전에 수행 될 것임을 알 수 있습니다. 그것은 너무 나쁘다 .net은 임시 매개 변수와 함수 반환 값의 개념을 가지고 있지 않기 때문에 ...
ref
를 클래스 객체로 달성 할 수 있습니다. 기본적으로 지역 변수, 매개 변수 및 함수 반환 값은 지속 가능 (기본값), 반환 가능 또는 임시 일 수 있습니다. 임시 범위의 물건을 현재 범위보다 오래 지속되는 것으로 복사하는 것은 금지되어 있습니다. 반환 가능한 것은 함수에서 반환 될 수 있다는 점을 제외하면 임시적인 것과 같습니다. 함수의 반환 값은 "반환 가능"매개 변수에 적용되는 가장 엄격한 제한 사항에 의해 제한됩니다.
기본 규칙은 다음과 같습니다.
모든 멤버 필드가 값 유형 인 경우 구조체를 만듭니다 .
하나의 멤버 필드가 참조 유형 인 경우 클래스를 작성하십시오 . 어쨌든 참조 유형 필드에는 힙 할당이 필요하기 때문입니다.
추방
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
}
string
은 의미 적으로 값과 동일하며 불변의 객체에 대한 참조를 필드에 저장하는 것은 힙 할당을 수반하지 않습니다. 노출 공용 필드 구조체 노출 공용 필드 클래스 오브젝트의 차이는 부호 계열 주어진 인 var q=p; p.X=4; q.X=5;
, p.X
경우 4 값을 갖 a
은 클래스 타입인지 5 구조형이고. 유형의 멤버를 편리하게 수정하려면 변경 사항 q
이 영향 을 줄지 여부에 따라 'class'또는 'struct'를 선택해야합니다 p
.
ArraySegment<T>
캡슐화합니다 T[]
. 구조 유형 KeyValuePair<TKey,TValue>
은 종종 클래스 유형과 함께 일반 매개 변수로 사용됩니다.
StructLayoutAttribute ( 일반적으로 PInvoke)를 사용하여 메모리 레이아웃을 명시 적으로 지정하려는 경우 "struct"를 사용해야합니다 .
편집 : 의견에 따르면 StructLayoutAttribute와 함께 클래스 또는 구조체를 사용할 수 있으며 이는 사실입니다. 실제로는 일반적으로 구조체를 사용합니다. 관리되지 않은 메서드 호출에 인수를 전달하는 경우 의미가있는 스택 대 힙에 할당됩니다.
바이너리 통신 형식을 패킹하거나 언팩하는 데 구조체를 사용합니다. 여기에는 디스크 읽기, 쓰기, DirectX 정점 목록, 네트워크 프로토콜 또는 암호화 / 압축 된 데이터 처리가 포함됩니다.
여기에 나와있는 세 가지 지침은이 맥락에서 유용하지 않았습니다. 특정 순서로 4 백 바이트의 내용을 작성해야 할 때 4 백 바이트 구조체를 정의하고 관련이없는 값으로 채울 것입니다. 당시 가장 의미있는 방식으로 설정했습니다. (좋아요, 4 백 바이트는 꽤 이상 할 것입니다. 그러나 생계를 위해 Excel 파일을 작성할 때, 최대 약 40 바이트의 구조체를 다루고있었습니다. BIFF 레코드의 크기가 얼마나 크니까요.)
PInvoke 목적으로 런타임 및 기타 여러 유형에서 직접 사용되는 값 유형을 제외하고 두 가지 시나리오에서만 값 유형을 사용해야합니다.
this
메서드를 호출하는 데 사용되는 임시 매개 변수 제외) 더 나은 성능을 제공합니다 . 클래스는 하나의 참조를 복제 할 수 있습니다.
.NET 지원 value types
과 reference types
(자바, 당신은 단지 참조 유형을 정의 할 수 있습니다). 인스턴스는 reference types
관리되는 힙에 할당되며 미해결 참조가없는 경우 가비지 수집됩니다. value types
반면에의 인스턴스 는에 할당 stack
되므로 할당 된 메모리는 범위가 종료되는 즉시 회수됩니다. 그리고 물론 value types
가치와 reference types
참조로 전달 하십시오. System.String을 제외한 모든 C # 기본 데이터 형식은 값 형식입니다.
struct over class를 사용할 때
C #에서 structs
are value types
는 클래스가 reference types
입니다. enum
키워드와 키워드를 사용하여 C #에서 값 유형을 만들 수 있습니다 struct
. value type
대신에를 사용 reference type
하면 관리되는 힙에있는 객체 수가 줄어들어 가비지 수집기 (GC)에 대한로드가 줄어들고 GC주기가 잦아지며 결과적으로 성능이 향상됩니다. 그러나 value types
단점도 있습니다. 큰 struct
것을 전달하는 것은 참조를 전달하는 것보다 확실히 비용이 많이 듭니다. 그것은 명백한 문제입니다. 다른 문제는와 관련된 오버 헤드입니다 boxing/unboxing
. 경우 당신은 무엇을 궁금해하는 boxing/unboxing
의미에 대한 좋은 설명에 대해 다음 링크를 따라 boxing
와unboxing
. 성능 외에도, 가치 의미론을 갖기 위해 단순히 유형이 필요한 경우 reference types
가 있습니다. value types
일반적 arrays
으로 이러한 유형의 복사 시맨틱이 필요하거나 자동 초기화가 필요한 경우 에만 사용해야 합니다 .
ref
. 모든 크기 구조 ref
를 전달하는 것은 값으로 클래스 참조를 전달하는 것과 같습니다. 크기 구조를 복사하거나 값으로 전달하는 것은 클래스 객체의 방어 적 복사를 수행하고 참조를 저장하거나 전달하는 것보다 저렴합니다. 큰 배 클래스는 (1) 클래스 (운전자 복사를 방지하도록,) 불변이며, 생성되는 각각의 인스턴스가 많이 주위에 전달되거나, ...의 값을 저장하기위한 구조체보다 나은
readOnlyStruct.someMember = 5;
만들 수 없습니다 someMember
읽기 전용 속성을하지만, 대신 필드합니다.
구조체 값 형식이다. 구조체를 새 변수에 할당하면 새 변수에 원본의 사본이 포함됩니다.
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
나는 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;
}
}
C # 또는 다른 .net 언어의 구조 유형은 일반적으로 고정 크기 값 그룹처럼 동작해야하는 항목을 보유하는 데 사용됩니다. 구조 유형의 유용한 측면은 구조 유형 인스턴스의 필드는 보관 위치를 수정하여 다른 방식으로 수정할 수 있다는 것입니다. 필드를 변경하는 유일한 방법은 완전히 새로운 인스턴스를 구성한 다음 구조체 할당을 사용하여 대상의 모든 필드를 새 인스턴스의 값으로 덮어 써서 변경하는 방식으로 구조를 코딩 할 수 있습니다. 구조체가 필드에 기본값이 아닌 인스턴스를 만들 수있는 방법을 제공하지 않는 한 구조체 자체가 가변 위치에 저장되어있는 경우 모든 필드를 변경할 수 있습니다.
구조에 개인 클래스 유형 필드가 포함되어 있고 자체 멤버를 랩핑 된 클래스 오브젝트의 필드로 경로 재지 정하는 경우 기본적으로 클래스 유형처럼 작동하도록 구조 유형을 설계 할 수 있습니다. 예를 들어, PersonCollection
힘의 서비스 특성 SortedByName
및 SortedById
하는에 "불변"참조 잡아 둘 PersonCollection
(자신의 생성자에서 설정) 및 구현 GetEnumerator
중 하나를 호출하여 creator.GetNameSortedEnumerator
또는 creator.GetIdSortedEnumerator
. 이러한 구조체는 PersonCollection
의 GetEnumerator
메서드가의 다른 메서드에 바인딩 된다는 점을 제외하고는에 대한 참조와 매우 유사하게 동작 합니다 PersonCollection
. 하나는 구조가 예를 들어 하나가 정의 할 수있다 (배열 부분 포장 할 수 ArrayRange<T>
홀드 것이다 구조 T[]
라는 Arr
, int를 Offset
, 및 INTLength
idx
0에서 ~ 사이 의 색인 에 대해 Length-1
액세스 할 수 있는 색인 된 속성이있는 Arr[idx+Offset]
). 불행히도, foo
그러한 구조의 읽기 전용 인스턴스 인 경우 현재 컴파일러 버전은 foo[3]+=4;
이러한 연산이의 필드에 쓰려고 시도하는지 여부를 결정할 방법이 없으므로 이와 같은 연산을 허용하지 않습니다 foo
.
변수 크기의 컬렉션을 보유하는 값 유형과 유사하게 동작하도록 구조체를 설계하는 것도 가능합니다 (구조가있을 때마다 복사되는 것처럼 보일 것입니다).하지만 그 작업을 수행하는 유일한 방법은 struct hold 참조는 참조를 변경시킬 수있는 모든 것에 노출됩니다. 예를 들어, 개인 배열을 보유하고 인덱스 된 "put"메소드가 하나의 변경된 요소를 제외하고 원래 배열의 내용과 동일한 새 배열을 작성하는 배열과 유사한 구조를 가질 수 있습니다. 불행히도 그러한 구조체를 효율적으로 수행하는 것은 다소 어려울 수 있습니다. 구조체 의미론이 편리 할 수있는 시간이 있지만 (예 : 발신자와 수신자 모두 외부 코드가 컬렉션을 수정하지 않는다는 것을 알고 배열과 같은 컬렉션을 루틴에 전달할 수 있음)
아냐-나는 규칙에 전적으로 동의하지 않는다. 그것들은 성능과 표준화로 고려해야 할 좋은 지침이지만 가능성을 고려하지는 않습니다.
응답에서 볼 수 있듯이 창의적인 방법으로 사용할 수있는 방법이 많이 있습니다. 따라서 이러한 지침은 성능과 효율성을 위해 항상 그와 같아야합니다.
이 경우 클래스를 사용하여 실제 객체를 더 큰 형태로 표현하고 구조체를 사용하여 더 정확한 용도의 작은 객체를 표현합니다. 당신이 말한 방식은 "보다 응집력있는 전체"입니다. 응집력있는 키워드입니다. 클래스는 더 객체 지향적 인 요소이지만 구조체는 더 작은 규모이지만 이러한 특성 중 일부를 가질 수 있습니다. IMO.
일반적인 정적 속성에 매우 빠르게 액세스 할 수있는 Treeview 및 Listview 태그에서 많이 사용합니다. 나는 항상이 정보를 다른 방법으로 얻기 위해 고군분투했다. 예를 들어, 데이터베이스 응용 프로그램에서 테이블, SP, 함수 또는 다른 개체가있는 Treeview를 사용합니다. 나는 구조체를 생성하고 채우고, 태그에 넣고, 뽑아 내고, 선택 데이터 등을 얻는다. 나는 수업으로 이것을하지 않을 것입니다!
나는 그것들을 작게 유지하고 단일 인스턴스 상황에서 사용하며 변경되지 않도록합니다. 메모리, 할당 및 성능을 알고 있어야합니다. 그리고 테스트가 필요합니다.
double
해당 좌표에 대한 값 조합을 사용하여 인스턴스를 작성할 수있는 경우 이러한 스펙은 멀티 스레드 동작에 대한 세부 사항을 제외하고는 노출 된 필드 구조체와 의미 적으로 동일하게 동작합니다 (불변 클래스는 어떤 경우에는 더 나은 반면 노출 된 필드 구조체는 다른 경우에는 더 좋을 것입니다. 소위 "불변"구조체는 모든 경우에 더 나쁘다).
내 규칙은
1, 항상 수업 사용;
2, 성능 문제가있는 경우 @IAbstract가 언급 한 규칙에 따라 struct 클래스를 변경 한 다음 이러한 변경 사항이 성능을 향상시킬 수 있는지 테스트합니다.
Foo
독립적 인 값 (예 : 점 좌표)의 고정 된 컬렉션을 캡슐화하여 때로는 그룹으로 전달하고 때로는 독립적으로 변경하려는 경우입니다. 나는 두 가지 목적을 간단한 노출 필드 구조체 (독립 변수의 고정 수집, 법안에 완벽하게 부합)와 거의 잘 결합하는 클래스를 사용하는 패턴을 찾지 못했습니다.
public readonly
생성하기 때문에 읽기 전용 특성이 실질적으로 어떤 이익을 위해 단순히 너무 많은 일을하다, 너무, 내 유형의 필드를.)
MyListOfPoint[3].Offset(2,3);
에 var temp=MyListOfPoint[3]; temp.Offset(2,3);
... 적용 할 때 가짜를 인 변환
Offset
방법에. 그러한 가짜 코드를 방지하는 올바른 방법은 구조체를 불필요하게 불변으로 만들지 말고 대신에 Offset
앞서 언급 한 변환을 금지하는 속성으로 태그를 지정 하는 것을 허용 해야합니다. 암시 적 숫자 변환도 호출이 분명한 경우에만 적용 할 수 있도록 태그를 지정할 수 있으면 훨씬 더 좋을 수 있습니다. foo(float,float)
및 foo(double,double)
에 대한 과부하가 float
있는 double
경우 a를 사용하려고 하면 종종 암시 적 변환을 적용하지 말고 대신 오류가 있어야한다고 생각합니다.
double
값을에 직접 할당 float
하거나 float
인수를 취할 수는 있지만 메소드를 전달할 수없는 메소드에 전달 double
하면 프로그래머가 의도 한대로 수행됩니다. 반대로 명시적인 타입 캐스트없이 float
표현식을 할당하는 double
것은 종종 실수입니다. 암시 적 double->float
변환을 허용 할 수있는 유일한 시간 은 이상적이지 않은 과부하가 선택 될 때입니다. 나는 그것을 막을 수있는 올바른 방법은 implcit double-> float을 금지하지 않아야하지만 변환을 허용하지 않는 속성으로 과부하를 태깅하는 것이 아니라고 주장했다.
클래스는 참조 유형입니다. 클래스의 객체가 생성 될 때 객체가 할당 된 변수는 해당 메모리에 대한 참조 만 보유합니다. 객체 참조가 새 변수에 할당되면 새 변수는 원래 객체를 나타냅니다. 하나의 변수를 통해 변경 한 사항은 다른 변수에 반영되므로 둘 다 동일한 데이터를 참조하기 때문입니다. 구조체는 값 형식입니다. 구조체가 생성 될 때 구조체가 할당 된 변수는 구조체의 실제 데이터를 유지합니다. 구조체가 새 변수에 할당되면 복사됩니다. 따라서 새 변수와 원래 변수에는 동일한 데이터의 두 개의 별도 사본이 포함됩니다. 한 사본을 변경해도 다른 사본에는 영향을 미치지 않습니다. 일반적으로 클래스는보다 복잡한 동작 또는 클래스 객체가 작성된 후 수정 될 데이터를 모델링하는 데 사용됩니다.
오해 # 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 #
좋은 첫 번째 근사치는 "never"라고 생각합니다.
좋은 초 근사치는 "never"라고 생각합니다.
성능에 필사적이라면 고려하고 항상 측정하십시오.
C # 구조체는 클래스의 간단한 대안입니다. 클래스와 거의 동일하게 수행 할 수 있지만 클래스가 아닌 구조체를 사용하는 것이 "비싸지 않습니다". 그 이유는 약간 기술적 인 것이지만 요약하자면 클래스의 새 인스턴스가 힙에 배치되고 새로 인스턴스화 된 구조체가 스택에 배치됩니다. 또한 클래스와 같은 구조체에 대한 참조를 다루지 않고 구조체 인스턴스와 직접 작업합니다. 이는 또한 구조체에 함수를 전달할 때 참조가 아니라 값을 기준으로한다는 것을 의미합니다. 기능 매개 변수에 관한 장에서 이에 대한 자세한 내용이 있습니다.
따라서 더 간단한 데이터 구조를 나타내려면 특히 구조체를 많이 인스턴스화 할 것임을 알고있는 경우 구조체를 사용해야합니다. .NET 프레임 워크에는 Microsoft가 클래스 대신 구조체, 예를 들어 Point, Rectangle 및 Color 구조체를 사용한 예제가 많이 있습니다.
가비지 수집 성능을 향상시키기 위해 Struct를 사용할 수 있습니다. 일반적으로 GC 성능에 대해 걱정할 필요는 없지만 킬러가 될 수있는 시나리오가 있습니다. 지연 시간이 짧은 응용 프로그램의 큰 캐시와 같습니다. 예를 보려면이 게시물을 참조하십시오.
http://00sharp.wordpress.com/2013/07/03/a-case-for-the-struct/
구조는 대부분 클래스 / 객체와 같습니다. 구조는 함수, 멤버를 포함 할 수 있으며 상속 될 수 있습니다. 그러나 구조는 C #에 있으며 데이터 보유 에만 사용됩니다 . 구조체는 클래스보다 적은 RAM을 사용 하며 가비지 수집기가 수집하기 가 더 쉽습니다 . 그러나 구조에서 함수를 사용할 때 컴파일러는 실제로 해당 구조를 클래스 / 객체와 매우 유사하게 취하므로 함수가 있는 것을 원하면 class / object를 사용하십시오 .
System.Drawing.Rectangle
이 세 가지 규칙을 모두 위반합니다.