답변:
MSDN에는 다음과 같은 대답이 있습니다. 클래스와 구조 간 선택 .
기본적으로이 페이지는 4 가지 항목 점검 목록을 제공하며 유형이 모든 기준을 충족하지 않는 한 클래스를 사용하도록 지시합니다.
유형에 다음 특성이 모두없는 한 구조를 정의하지 마십시오.
- 기본 유형 (정수, 이중 등)과 유사한 단일 값을 논리적으로 나타냅니다.
- 인스턴스 크기가 16 바이트보다 작습니다.
- 불변입니다.
- 자주 박스에 넣을 필요는 없습니다.
ref
그렇게하는 것이 합리적 때마다 매개 변수를 설정합니다. 값이 4 개인 필드를 가진 구조체를 수정 된 버전을 반환하는 메서드에 전달하는 것보다 4,000 개의 필드를 가진 구조체를 ref 매개 변수로 ref 매개 변수로 전달하는 방법을 변경하십시오.
이전 답변을 읽지 않은 것에 놀랐습니다. 가장 중요한 측면을 고려합니다.
ID가없는 유형을 원할 때 구조체를 사용합니다. 예를 들어 3D 점 :
public struct ThreeDimensionalPoint
{
public readonly int X, Y, Z;
public ThreeDimensionalPoint(int x, int y, int z)
{
this.X = x;
this.Y = y;
this.Z = z;
}
public override string ToString()
{
return "(X=" + this.X + ", Y=" + this.Y + ", Z=" + this.Z + ")";
}
public override int GetHashCode()
{
return (this.X + 2) ^ (this.Y + 2) ^ (this.Z + 2);
}
public override bool Equals(object obj)
{
if (!(obj is ThreeDimensionalPoint))
return false;
ThreeDimensionalPoint other = (ThreeDimensionalPoint)obj;
return this == other;
}
public static bool operator ==(ThreeDimensionalPoint p1, ThreeDimensionalPoint p2)
{
return p1.X == p2.X && p1.Y == p2.Y && p1.Z == p2.Z;
}
public static bool operator !=(ThreeDimensionalPoint p1, ThreeDimensionalPoint p2)
{
return !(p1 == p2);
}
}
이 구조체의 인스턴스가 두 개인 경우 메모리의 단일 데이터인지 두 개인 지 상관하지 않습니다. 당신은 그들이 보유하고있는 가치에 관심이 있습니다.
return false
거기에 있었어야했던 것이 지금 바로 수정되었습니다.
Bill Wagner는 그의 책 "effective c #"( http://www.amazon.com/Effective-Specific-Ways-Improve-Your/dp/0321245660 ) 에서 이에 관한 장을 가지고 있습니다. 그는 다음 원리를 사용하여 결론을 맺습니다.
- 유형 데이터 스토리지의 주요 책임은 무엇입니까?
- 공용 인터페이스는 데이터 멤버에 액세스하거나 수정하는 속성으로 만 정의됩니까?
- 타입에 서브 클래스가 절대 없을까요?
- 유형이 다형성으로 처리되지 않습니까?
네 가지 질문 모두에 '예'라고 대답하면 구조체를 사용하십시오. 그렇지 않으면 클래스를 사용하십시오.
참조 유형 대신 값 유형 시맨틱을 원할 때 구조체를 사용하십시오. Structs는 값별로 복사되므로주의하십시오!
예를 들어 이전 질문을 참조하십시오.
다음과 같은 경우 수업을 이용하십시오 :
다음과 같은 경우 구조를 사용하십시오.
엔터티를 변경할 수없는 경우 구조체 또는 클래스를 사용할지 여부에 대한 문제는 일반적으로 의미가 아니라 성능 중 하나입니다. 32/64 비트 시스템에서 클래스 참조는 클래스의 정보량에 관계없이 4/8 바이트를 저장해야합니다. 클래스 참조를 복사하려면 4/8 바이트를 복사해야합니다. 다른 한편으로, 모든 별개의클래스 인스턴스는 보유하고있는 정보와 이에 대한 참조의 메모리 비용 외에 8/16 바이트의 오버 헤드를 갖습니다. 각각 4 개의 32 비트 정수를 보유하는 500 개의 엔티티 배열을 원한다고 가정하십시오. 엔티티가 구조 유형 인 경우 배열은 500 개의 엔티티가 모두 동일하거나 모두 다르거 나 사이에 관계없이 8,000 바이트를 필요로합니다. 엔티티가 클래스 유형 인 경우 500 개의 참조 배열에는 4,000 바이트가 필요합니다. 이러한 참조가 모두 다른 객체를 가리키는 경우 객체는 각각 추가 24 바이트 (500 개당 12,000 바이트), 총 16,000 바이트 (구조 유형의 스토리지 비용의 두 배)가 필요합니다. 반면에 코드가 하나의 객체 인스턴스를 생성 한 다음 500 개의 모든 배열 슬롯에 대한 참조를 복사 한 경우 총 비용은 해당 인스턴스의 24 바이트, 4, 배열의 경우 000-총 4,024 바이트입니다. 주요 절감 효과. 마지막 상황뿐만 아니라 거의 상황이 해결되지 않지만 경우에 따라 공유를 가치있게 만들기 위해 충분한 배열 슬롯에 일부 참조를 복사 할 수 있습니다.
엔티티가 변경 가능 해야하는 경우 클래스 또는 구조체를 사용할지 여부에 대한 질문이 더 쉽습니다. "Thing"은 x라는 정수 필드를 가진 구조체 또는 클래스이고 다음 코드를 수행한다고 가정합니다.
일 t1, t2; ... t2 = t1; t2.x = 5;
후자의 진술이 t1.x에 영향을 미치 길 원합니까?
Thing이 클래스 유형 인 경우 t1과 t2는 동일하므로 t1.x와 t2.x도 동일합니다. 따라서 두 번째 문장은 t1.x에 영향을 미칩니다. Thing이 구조 유형 인 경우 t1과 t2는 서로 다른 인스턴스가되므로 t1.x와 t2.x는 서로 다른 정수를 나타냅니다. 따라서 두 번째 문장은 t1.x에 영향을 미치지 않습니다.
.net에는 구조체 돌연변이 처리에 약간의 단점이 있지만 가변 구조와 가변 클래스는 근본적으로 다른 동작을합니다. 값 유형 동작을 원할 경우 ( "t2 = t1"은 t1과 t2를 별개의 인스턴스로 남겨두고 t1에서 t2로 데이터를 복사 함을 의미 함) .net에서 값 유형을 처리 할 때 문제가 생길 수있는 경우 구조. 밸류 타입 시맨틱을 원하지만 .net의 쿼크로 인해 애플리케이션에서 밸류 타입 시맨틱이 깨질 경우 클래스를 사용하고 중얼 거린다.
실제로 행동이 필요하지 않지만 간단한 배열이나 사전보다 더 많은 구조가 필요합니다.
후속 조치 이것은 일반적으로 구조체를 생각하는 방식입니다. 나는 그들이 방법을 가질 수 있다는 것을 알고 있지만, 전반적인 정신 구분을 유지하는 것을 좋아합니다.
오래된 주제이지만 간단한 벤치 마크 테스트를 제공하고자했습니다.
두 개의 .cs 파일을 만들었습니다.
public class TestClass
{
public long ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
과
public struct TestStruct
{
public long ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
벤치 마크 실행 :
결과 :
BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18362
Intel Core i5-8250U CPU 1.60GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=3.1.101
[Host] : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT [AttachedDebugger]
DefaultJob : .NET Core 3.1.1 (CoreCLR 4.700.19.60701, CoreFX 4.700.19.60801), X64 RyuJIT
| Method | Mean | Error | StdDev | Ratio | RatioSD | Rank | Gen 0 | Gen 1 | Gen 2 | Allocated |
|--------------- |---------------:|--------------:|--------------:|----------:|--------:|-----:|---------:|------:|------:|----------:|
| UseStruct | 0.0000 ns | 0.0000 ns | 0.0000 ns | 0.000 | 0.00 | 1 | - | - | - | - |
| UseClass | 8.1425 ns | 0.1873 ns | 0.1839 ns | 1.000 | 0.00 | 2 | 0.0127 | - | - | 40 B |
| Use100Struct | 36.9359 ns | 0.4026 ns | 0.3569 ns | 4.548 | 0.12 | 3 | - | - | - | - |
| Use100Class | 759.3495 ns | 14.8029 ns | 17.0471 ns | 93.144 | 3.24 | 4 | 1.2751 | - | - | 4000 B |
| Use10000Struct | 3,002.1976 ns | 25.4853 ns | 22.5920 ns | 369.664 | 8.91 | 5 | - | - | - | - |
| Use10000Class | 76,529.2751 ns | 1,570.9425 ns | 2,667.5795 ns | 9,440.182 | 346.76 | 6 | 127.4414 | - | - | 400000 B |
흠 ...
구조체 대 클래스 사용에 대한 가비지 수집을 인수로 사용하지 않습니다. 관리되는 힙은 스택과 매우 유사하게 작동합니다. 개체를 만들면 힙의 맨 위에 놓이므로 스택에 할당하는 것만 큼 빠릅니다. 또한 개체의 수명이 짧고 GC주기에서 살아남지 못하면 GC가 여전히 액세스 가능한 메모리에서만 작동하므로 할당 해제가 무료입니다. (MSDN을 검색하십시오. .NET 메모리 관리에 대한 일련의 기사가 있습니다.
내가 구조체를 사용하는 대부분의 경우, 나중에 참조 의미론을 갖는 것이 일을 조금 더 단순하게 만들었 음을 알게 되었기 때문에 그렇게하기 위해 스스로를 차 버린다.
어쨌든 위에 게시 된 MSDN 기사의 4 가지 사항은 좋은 지침으로 보입니다.
class MutableHolder<T> { public T Value; MutableHolder(T value) {Value = value;} }
하면 a MutableHolder<T>
는 클래스 의미가 변경 가능한 객체입니다 ( T
구조체 또는 불변 클래스 유형 인 경우에도 마찬가지 입니다).
가장 좋은 대답은 필요한 속성 모음, 클래스가 속성 및 동작 모음 인 경우 클래스를 사용하는 것입니다.