programing

.NET에서 struct와 class의 차이점은 무엇입니까?

sourcejob 2023. 5. 29. 10:36
반응형

.NET에서 struct와 class의 차이점은 무엇입니까?

.NET에서 struct와 class의 차이점은 무엇입니까?

.NET에는 참조 유형과 값 유형의 두 가지 범주가 있습니다.

구조체는 값 유형이고 클래스는 참조 유형입니다.

일반적인 차이점은 참조 유형이 힙에 상주하고 값 유형이 인라인에 상주한다는 것입니다. 즉, 변수 또는 필드가 정의된 위치에 상관없이 값 유형이 정의됩니다.

유형을 포함하는 변수에는 전체 값 유형 값이 포함됩니다.구조체의 경우 모든 필드와 함께 전체 구조체가 변수에 포함된다는 것을 의미합니다.

참조 유형을 포함하는 변수에는 포인터 또는 실제 값이 있는 메모리의 다른 위치에 대한 참조가 포함됩니다.

이는 다음과 같은 이점을 제공합니다.

  • 유형에는 항상 값이 포함됩니다.
  • 참조 유형에는 null-참조가 포함될 수 있습니다. 즉, 현재로서는 아무것도 참조하지 않습니다.

내부적으로 참조 유형은 포인터로 구현되며, 변수 할당이 작동하는 방식을 알고 있으면 다른 동작 패턴이 있습니다.

  • 유형 변수의 내용을 다른 변수로 복사하고 전체 내용을 새 변수로 복사하여 두 변수를 구분합니다.즉, 복사 후에 한 변경사항이 다른 변경사항에 영향을 미치지 않습니다.
  • 참조 유형 변수의 내용을 다른 변수로 복사하고 참조를 복사합니다. 즉, 실제 데이터의 동일한 저장소에 대한 두 개의 참조가 생성됩니다.다시 말해, 복사 후 한 참조의 데이터를 변경하면 다른 참조에도 영향을 주는 것처럼 보이지만, 두 위치 모두에서 동일한 데이터를 보고 있기 때문입니다.

변수 또는 필드를 선언할 때 두 유형의 차이점은 다음과 같습니다.

  • 변수: 유형은 스택에 살고, 참조 유형은 실제 메모리가 있는 힙 메모리의 어딘가에 포인터로 스택에 삽니다(Eric Lipperts 기사 시리즈: 스택은 구현 세부 사항입니다.)
  • class/message-field: 유형은 유형 내부에 완전히 존재하며 참조 유형은 실제 메모리가 있는 힙 메모리의 특정 위치에 대한 포인터로 유형 내부에 존재합니다.

각 항목에 대한 요약:

클래스만:

  • 상속을 지원할 수 있습니다.
  • 참조(포인터) 유형
  • 참조는 null일 수 있습니다.
  • 새 인스턴스당 메모리 오버헤드 발생

구조물만:

  • 상속을 지원할 수 없습니다.
  • 값 유형
  • 정수와 같은 값으로 전달됩니다.
  • Nullable을 사용하지 않는 한 Null 참조를 가질 수 없습니다.
  • '박스' 상태가 아니면 새 인스턴스당 메모리 오버헤드가 없습니다.

클래스 및 구조체 모두:

  • 복합 데이터 유형은 일반적으로 논리적 관계가 있는 몇 가지 변수를 포함하는 데 사용됩니다.
  • 메서드 및 이벤트를 포함할 수 있습니다.
  • 인터페이스 지원 가능

구조체와 클래스의 차이:

  • 구조체는 값 유형인 반면 클래스는 참조 유형입니다.
  • 구조체는 스택에 저장되는 반면 클래스는 힙에 저장됩니다.
  • 값 유형은 선언된 메모리에 값을 유지하지만 참조 유형은 메모리에 있는 개체에 대한 참조를 유지합니다.
  • 값 유형은 스코프가 손실된 직후에 삭제되는 반면 참조 유형은 스코프가 손실된 후에만 변수를 삭제합니다.개체는 나중에 가비지 수집기에 의해 삭제됩니다.
  • 구조체를 다른 구조체로 복사하면 해당 구조체의 새 사본이 만들어집니다.수정된 구조는 다른 구조의 값에 영향을 주지 않습니다.
  • 클래스를 다른 클래스로 복사하면 참조 변수만 복사됩니다.
  • 두 참조 변수는 힙에서 동일한 개체를 가리킵니다.한 변수에 대한 변경 내용은 다른 참조 변수에 영향을 줍니다.
  • 구조체에는 소멸자를 사용할 수 없지만 클래스에는 소멸자를 사용할 수 있습니다.
  • 구조체는 명시적 매개 변수 없는 생성자를 가질없지만 클래스는 가질 수 있습니다.구조체는 상속을 지원하지 않지만 클래스는 상속을 지원합니다.둘 다 인터페이스에서 상속을 지원합니다.
  • 구조물은 밀폐형입니다.

.NET에서 구조체와 클래스 선언은 참조 유형과 값 유형을 구별합니다.

참조 유형을 넘길 때 실제로 저장되는 것은 하나뿐입니다.인스턴스에 액세스하는 모든 코드는 동일한 코드에 액세스합니다.

값을 입력하면 각 값이 복사본이 됩니다.모든 코드가 자체 복사본에서 작동하고 있습니다.

예를 들어 다음과 같이 표시할 수 있습니다.

struct MyStruct 
{
    string MyProperty { get; set; }
}

void ChangeMyStruct(MyStruct input) 
{ 
   input.MyProperty = "new value";
}

...

// Create value type
MyStruct testStruct = new MyStruct { MyProperty = "initial value" }; 

ChangeMyStruct(testStruct);

// Value of testStruct.MyProperty is still "initial value"
// - the method changed a new copy of the structure.

수업의 경우 이것은 다를 것입니다.

class MyClass 
{
    string MyProperty { get; set; }
}

void ChangeMyClass(MyClass input) 
{ 
   input.MyProperty = "new value";
}

...

// Create reference type
MyClass testClass = new MyClass { MyProperty = "initial value" };

ChangeMyClass(testClass);

// Value of testClass.MyProperty is now "new value" 
// - the method changed the instance passed.

클래스는 null일 수 없습니다. 참조가 null을 가리킬 수 있습니다.

구조체는 실제 값입니다. 구조체는 비어 있을 수 있지만 null일 수 없습니다.이러한 이유로 구조체에는 항상 매개 변수가 없는 기본 생성자가 있습니다. '시작 값'이 필요합니다.

Microsoft의 클래스와 구조 선택에서...

경험에 비추어 볼 때, 프레임워크의 대부분의 유형은 클래스여야 합니다.그러나 값 유형의 특성으로 인해 구조체를 사용하는 것이 더 적합한 경우가 있습니다.

클래스 대신 구조체를 고려합니다.

  • 유형의 인스턴스가 작고 일반적으로 수명이 짧거나 일반적으로 다른 개체에 포함된 경우.

X 유형에 다음 특성이 모두 없는 한 구조물을 피하십시오.

  • 기본 유형(int, double 등)과 유사한 단일 값을 논리적으로 나타냅니다.
  • 인스턴스 크기가 16바이트 미만입니다.
  • 그것은 불변입니다.(변경할 수 없음)
  • 그것은 자주 상자에 넣을 필요가 없을 것입니다.

I ♥ 시각화, 그리고 여기서 구조클래스 간의 기본적인 차이점을 보여주는 시각화를 만들었습니다.여기에 이미지 설명 입력


그리고 만약을 위해 텍스트 표현 ;)

+--------------------------------------------------+------+----------------------------------------------+
|                      Struct                      |      |                      Class                    |
+--------------------------------------------------+------+----------------------------------------------+
| - 1 per Thread.                                  |      | - 1 per application.                         |
|                                                  |      |                                              |
| - Holds value types.                             |      | - Holds reference types.                     |
|                                                  |      |                                              |
| - Types in the stack are positioned              |      | - No type ordering (data is fragmented).     |
|   using the LIFO principle.                      |      |                                              |
|                                                  |      |                                              |
| - Can't have a default constructor and/or        |      | - Can have a default constructor             |
|   finalizer(destructor).                         |      |   and/or finalizer.                          |
|                                                  |      |                                              |
| - Can be created with or without a new operator. |      | - Can be created only with a new operator.   |
|                                                  |      |                                              |
| - Can't derive from the class or struct          |  VS  | - Can have only one base class and/or        |
|   but can derive from the multiple interfaces.   |      |   derive from multiple interfaces.           |
|                                                  |      |                                              |
| - The data members can't be protected.           |      | - Data members can be protected.             |
|                                                  |      |                                              |
| - Function members can't be                      |      | - Function members can be                    |
|   virtual or abstract.                           |      |   virtual or abstract.                       |
|                                                  |      |                                              |
| - Can't have a null value.                       |      | - Can have a null value.                     |
|                                                  |      |                                              |
| - During an assignment, the contents are         |      | - Assignment is happening                    |
|   copied from one variable to another.           |      |   by reference.                              |
+--------------------------------------------------+------+----------------------------------------------+

자세한 내용은 아래를 참조하십시오.

구조 학급
유형 가치형 참조형
어디에 포함하는 형식의 스택/인라인 온 힙
할당 취소 스택 풀림/포함 유형이 할당 취소됨 수집된 가비지
배열 인라인에서 요소는 값 유형의 실제 인스턴스입니다. 라인을 벗어납니다. 요소는 힙에 있는 참조 유형의 인스턴스에 대한 참조일 뿐입니다.
알델 코스트 저렴한 할당-할당 해제 비싼 할당-할당 해제
메모리 사용량 구현하는 중 될 때 됩니다.
때 해제됨 값 유 형 할 트 상 에 서 자 짐 꺼
컬렉션이기 인 영향을 줍니다
복싱-언-복싱 금지
과제들 전체 데이터 복사 참조를 복사
인스턴스로 변경 복사본에 영향을 미치지 않습니다. 인스턴스를 가리키는 모든 참조에 영향을 줍니다.
변이성 불변이어야 함 무터블
인구. 경우에 따라 프레임워크의 대부분 유형은 클래스여야 합니다.
라이프타임 단명 장수
디스트럭터 가질 수 없음 가질 수 있음
상속 인터페이스에서만 사용 전폭적인
다형성 아니요. 네.
봉인됨 네. 이 있을 때sealed 키드워(C#), 또는Sealed 설정(F#)
생성자 명시적인 매개 변수 없는 생성자를 가질 수 없습니다. 모든 생성자
Null 할당 null 가능한 물음표로 표시된 경우 예(C#8+ 및 F#5+에서 무효 물음표로 표시된 경우)
추상적인 아니요. 이 있을 때abstract 키드워(C#), 또는AbstractClass 설정(F#)
구성원 액세스 수정자 public,private,internal public,protected,internal,protected internal,private protected

1 사용nullF#에서는 권장되지 않습니다. 대신 옵션 유형을 사용하십시오.

다른 답변에 설명된 모든 차이점 외에도 다음이 있습니다.

  1. 구조체는 명시적인 매개 변수 없는 생성자를 가질없는 반면, 클래스는
  2. 구조체는 소멸자를 가질없는 반면, 클래스는 소멸자를 가질 수 있습니다.
  3. 구조체는 다른 구조체나 클래스에서 상속할 수 없지만 클래스는 다른 클래스에서 상속할 수 있습니다. (구조체와 클래스 모두 인터페이스에서 구현할 수 있습니다.)

모든 차이점을 설명하는 비디오를 찾고 있다면 파트 29 - C# 튜토리얼 - C#의 클래스와 구조체 간의 차이를 확인할 수 있습니다.

클래스 인스턴스는 관리되는 힙에 저장됩니다.인스턴스를 '포함'하는 모든 변수는 힙의 인스턴스에 대한 참조일 뿐입니다.개체를 메서드에 전달하면 개체 자체가 아니라 참조 복사본이 전달됩니다.

구조(기술적으로, 값 유형)는 원시 유형과 매우 유사하게 사용되는 모든 위치에 저장됩니다.내용은 언제든지 사용자 정의된 복사 생성자를 호출하지 않고 런타임에 의해 복사될 수 있습니다.값 유형을 메서드에 전달하려면 사용자 지정 가능한 코드를 호출하지 않고 전체 값을 복사해야 합니다.

C++/CLI 이름으로 더 잘 구분됩니다. "ref class"는 첫 번째로 설명된 클래스이고 "value class"는 두 번째로 설명된 클래스입니다.C#에서 사용하는 키워드 "class"와 "struct"는 단순히 배워야 하는 것입니다.

구조 대 클래스

구조는 값 유형이므로 스택에 저장되지만 클래스는 참조 유형이며 힙에 저장됩니다.

구조는 상속과 다형성을 지원하지 않지만 클래스는 둘 다 지원합니다.

기본적으로 모든 구조체 구성원은 공용이지만 클래스 구성원은 기본적으로 비공개입니다.

구조체는 값 형식이므로 구조체 개체에 null을 할당할 수 없지만 클래스의 경우는 그렇지 않습니다.

다른 답변에 덧붙여 한 가지 주목할 만한 근본적인 차이점이 있습니다. 즉, 성능에 큰 영향을 미칠 수 있기 때문에 데이터가 어레이 내에 저장되는 방식입니다.

  • 구조체의 경우 배열에 구조체의 인스턴스(instance)가 포함됩니다.
  • 클래스가 있는 경우 어레이에는 메모리의 다른 위치에 있는 클래스 인스턴스에 대한 포인터가 포함됩니다.

구조체의 배열은 메모리에서 이렇게 보입니다.

[struct][struct][struct][struct][struct][struct][struct][struct]

반면에 수업의 배열은 이렇게 보입니다.

[pointer][pointer][pointer][pointer][pointer][pointer][pointer][pointer]

클래스 배열을 사용하면 관심 있는 값이 배열 내에 저장되지 않고 메모리의 다른 곳에 저장됩니다.

대부분의 애플리케이션에서 이 차이는 실제로는 중요하지 않지만 고성능 코드에서는 메모리 내 데이터의 인접성에 영향을 미치고 CPU 캐시의 성능에 큰 영향을 미칩니다.struct를 사용할 수 있거나 사용해야 할 때 클래스를 사용하면 CPU의 캐시 누락 수가 크게 증가합니다.

최신 CPU가 수행하는 가장 느린 작업은 숫자를 크런치하지 않고 메모리에서 데이터를 가져오는 것이며, L1 캐시 적중은 RAM에서 데이터를 읽는 것보다 몇 배 더 빠릅니다.

테스트할 수 있는 코드가 있습니다.내 컴퓨터에서 클래스 배열을 반복하는 것은 구조체 배열보다 최대 3배 더 오래 걸립니다.

    private struct PerformanceStruct
    {
        public int i1;
        public int i2;
    }

    private class PerformanceClass
    {
        public int i1;
        public int i2;
    }

    private static void DoTest()
    {
        var structArray = new PerformanceStruct[100000000];
        var classArray = new PerformanceClass[structArray.Length];

        for (var i = 0; i < structArray.Length; i++)
        {
            structArray[i] = new PerformanceStruct();
            classArray[i] = new PerformanceClass();
        }

        long total = 0;
        var sw = new Stopwatch();
        sw.Start();
        for (var loops = 0; loops < 100; loops++)
        for (var i = 0; i < structArray.Length; i++)
        {
            total += structArray[i].i1 + structArray[i].i2;
        }

        sw.Stop();
        Console.WriteLine($"Struct Time: {sw.ElapsedMilliseconds}");
        sw = new Stopwatch();
        sw.Start();
        for (var loops = 0; loops < 100; loops++)
        for (var i = 0; i < classArray.Length; i++)
        {
            total += classArray[i].i1 + classArray[i].i2;
        }

        Console.WriteLine($"Class Time: {sw.ElapsedMilliseconds}");
    }

우선, 구조는 참조가 아닌 가치로 전달됩니다.구조는 비교적 단순한 데이터 구조에 적합한 반면, 클래스는 구조적 관점에서 다형성 및 상속을 통해 훨씬 더 많은 유연성을 가집니다.

다른 사람들은 아마 저보다 더 자세히 알려줄 수 있겠지만, 저는 제가 추구하는 구조가 단순할 때 구조를 사용합니다.

완벽하게 하기 위해, 사용할 때 또 다른 차이점이 있습니다.Equals메서드, 모든 클래스 및 구조체에 상속됩니다.

클래스와 구조가 있다고 가정해 보겠습니다.

class A{
  public int a, b;
}
struct B{
  public int a, b;
}

그리고 메인 메소드에는 4개의 객체가 있습니다.

static void Main{
  A c1 = new A(), c2 = new A();
  c1.a = c1.b = c2.a = c2.b = 1;
  B s1 = new B(), s2 = new B();
  s1.a = s1.b = s2.a = s2.b = 1;
}

그러면:

s1.Equals(s2) // true
s1.Equals(c1) // false
c1.Equals(c2) // false
c1 == c2 // false

따라서 구조는 점(x 및 y 좌표 저장)과 같은 숫자와 유사한 객체에 적합합니다.그리고 수업은 다른 사람들에게 적합합니다.두 사람이 이름, 키, 몸무게가 같아도 여전히 두 사람입니다.

  1. 클래스에 선언된 이벤트는 잠금을 통해 += 및 -= 액세스가 자동으로 잠기므로 스레드가 안전합니다(정적 이벤트는 클래스 유형에서 잠깁니다).구조체에 선언된 이벤트에는 += 및 -= 액세스가 자동으로 잠기지 않습니다.참조 유형 식을 잠글 수만 있으므로 구조체에 대한 잠금(이)은 작동하지 않습니다.

  2. 생성자가 직접 또는 간접적으로 참조 유형 인스턴스를 생성하지 않는 한 구조 인스턴스를 생성하면 가비지 컬렉션이 발생할 수 없지만 참조 유형 인스턴스를 생성하면 가비지 컬렉션이 발생할 수 있습니다.

  3. 구조체에는 항상 공용 기본 생성자가 내장되어 있습니다.

    class DefaultConstructor
    {
        static void Eg()
        {
            Direct     yes = new   Direct(); // Always compiles OK
            InDirect maybe = new InDirect(); // Compiles if constructor exists and is accessible
            //...
        }
    }
    

    이것은 구조체는 항상 인스턴스화 가능한 반면, 클래스는 모든 생성자가 비공개일 수 있기 때문에 그렇지 않을 수 있음을 의미합니다.

    class NonInstantiable
    {
        private NonInstantiable() // OK
        {
        }
    }
    
    struct Direct
    {
        private Direct() // Compile-time error
        {
        }
    }
    
  4. 구조체에는 소멸자를 사용할 수 없습니다.소멸자는 객체의 재지정일 뿐입니다.위장하여 완료하며, 값 형식인 구조체는 가비지 수집 대상이 아닙니다.

    struct Direct
    {
        ~Direct() {} // Compile-time error
    }
    class InDirect
    {
        ~InDirect() {} // Compiles OK
    }
    
    And the CIL for ~Indirect() looks like this:
    
    .method family hidebysig virtual instance void
            Finalize() cil managed
    {
      // ...
    } // end of method Indirect::Finalize
    
  5. 구조체는 암묵적으로 봉인되어 있지만 클래스는 봉인되어 있지 않습니다.
    구조는 추상적일 수 없고, 클래스는 추상적일 수 있습니다.
    구조체는 생성자에서 base()를 호출할 수 없지만 명시적인 base 클래스가 없는 클래스는 호출할 수 없습니다.
    구조체는 다른 클래스를 확장할 수 없고 클래스는 확장할 수 없습니다.
    구조체는 보호된 구성원(예: 필드, 중첩된 유형)을 클래스로 선언할 수 없습니다.
    구조체는 추상 함수 구성원을 선언할 수 없고, 추상 클래스는 선언할 수 있습니다.
    구조체는 가상 함수 구성원을 선언할 수 없고 클래스는 선언할 수 없습니다.
    구조체는 밀봉된 함수 구성원을 선언할 수 없고 클래스는 선언할 수 없습니다.
    구조체는 재정의 함수 구성원을 선언할 수 없고 클래스는 선언할 수 있습니다.
    이 규칙의 한 가지 예외는 구조체가 시스템의 가상 메서드를 재정의할 수 있다는 것입니다.개체, viz, Equals(), GetHashCode() 및 ToString() 있습 다니이다있니습▁to,.

앞서 언급한 바와 같이:클래스는 참조 유형이고 Structs는 결과가 모두 포함된 값 유형입니다.

규칙적으로 프레임워크 설계 지침은 다음과 같은 경우 클래스 대신 Structs를 사용할 것을 권장합니다.

  • 인스턴스 크기가 16바이트 미만입니다.
  • 기본 유형(int, double 등)과 유사한 단일 값을 논리적으로 나타냅니다.
  • 그것은 불변입니다.
  • 자주 상자에 넣을 필요가 없습니다.

액세스 지정자의 기본적인 차이점과 위에서 언급된 몇 가지 이외에도 위에서 언급된 몇 가지 주요 차이점을 출력이 있는 코드 샘플과 함께 추가하여 참조와 값에 대한 보다 명확한 아이디어를 제공하고자 합니다.

구조:

  • 값 유형이며 힙 할당이 필요하지 않습니다.
  • 메모리 할당이 달라 스택에 저장됩니다.
  • 소규모 데이터 구조에 유용합니다.
  • 성능에 영향을 미치며, 메소드에 값을 전달하면 전체 데이터 구조가 전달되고 모든 것이 스택으로 전달됩니다.
  • 생성자는 단순히 구조 값 자체를 반환하고(일반적으로 스택의 임시 위치에 있음), 필요에 따라 이 값이 복사됩니다.
  • 변수에는 각각 고유한 데이터 복사본이 있으며, 한 변수에 대한 작업이 다른 변수에 영향을 미칠 수 없습니다.
  • 사용자 지정 상속을 지원하지 않으며 유형 개체에서 암시적으로 상속됩니다.

클래스:

  • 참조 유형 값
  • 힙에 저장됨
  • 동적으로 할당된 개체에 대한 참조 저장
  • 생성자가 새 연산자를 사용하여 호출되지만 힙에 메모리를 할당하지 않습니다.
  • 여러 변수가 동일한 개체에 대한 참조를 가질 수 있습니다.
  • 한 변수에 대한 작업이 다른 변수에서 참조하는 개체에 영향을 줄 수 있습니다.

코드 샘플

    static void Main(string[] args)
    {
        //Struct
        myStruct objStruct = new myStruct();
        objStruct.x = 10;
        Console.WriteLine("Initial value of Struct Object is: " + objStruct.x);
        Console.WriteLine();
        methodStruct(objStruct);
        Console.WriteLine();
        Console.WriteLine("After Method call value of Struct Object is: " + objStruct.x);
        Console.WriteLine();

        //Class
        myClass objClass = new myClass(10);
        Console.WriteLine("Initial value of Class Object is: " + objClass.x);
        Console.WriteLine();
        methodClass(objClass);
        Console.WriteLine();
        Console.WriteLine("After Method call value of Class Object is: " + objClass.x);
        Console.Read();
    }
    static void methodStruct(myStruct newStruct)
    {
        newStruct.x = 20;
        Console.WriteLine("Inside Struct Method");
        Console.WriteLine("Inside Method value of Struct Object is: " + newStruct.x);
    }
    static void methodClass(myClass newClass)
    {
        newClass.x = 20;
        Console.WriteLine("Inside Class Method");
        Console.WriteLine("Inside Method value of Class Object is: " + newClass.x);
    }
    public struct myStruct
    {
        public int x;
        public myStruct(int xCons)
        {
            this.x = xCons;
        }
    }
    public class myClass
    {
        public int x;
        public myClass(int xCons)
        {
            this.x = xCons;
        }
    }

산출량

Struct Object의 초기 값: 10

Struct Object의 Inside Struct Method Inside Method 값은 다음과 같습니다.

Struct Object의 메서드 호출 값은 다음과 같습니다. 10

클래스 개체의 초기 값: 10

클래스 개체의 Inside Class Method Inside

클래스 개체의 메서드 호출 값 다음은 20입니다.

여기서 값별 호출과 참조별 호출의 차이를 명확하게 확인할 수 있습니다.

클래스 대 구조 퍼즐의 한 가지 흥미로운 사례가 있습니다. 즉, 방법에서 여러 결과를 반환해야 하는 상황입니다. 사용할 것을 선택하십시오.ValueTuple 스토리를 알고 있다면 ValueTuple(구조)이 Tuple(클래스)보다 더 효과적이어야 하기 때문에 추가되었다는 것을 알 수 있습니다.하지만 숫자로 무엇을 의미할까요?두 가지 검정: 하나는 필드가 2개인 구조/클래스이고, 다른 하나는 필드가 8개인 구조/클래스입니다(차원이 4개 이상인 구조/클래스는 프로세서 틱 측면에서 구조보다 더 효과적이어야 하지만 물론 GC 부하도 고려해야 합니다).

추신: '수집이 있는 벡터 클래스'에 대한 또 다른 벤치마크는 https://stackoverflow.com/a/45276657/506147 입니다.

BenchmarkDotNet=v0.10.10, OS=Windows 10 Redstone 2 [1703, Creators Update] (10.0.15063.726)
Processor=Intel Core i5-2500K CPU 3.30GHz (Sandy Bridge), ProcessorCount=4
Frequency=3233540 Hz, Resolution=309.2586 ns, Timer=TSC
.NET Core SDK=2.0.3
  [Host] : .NET Core 2.0.3 (Framework 4.6.25815.02), 64bit RyuJIT
  Clr    : .NET Framework 4.7 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.2115.0
  Core   : .NET Core 2.0.3 (Framework 4.6.25815.02), 64bit RyuJIT


            Method |  Job | Runtime |     Mean |     Error |    StdDev |      Min |      Max |   Median | Rank |  Gen 0 | Allocated |
------------------ |----- |-------- |---------:|----------:|----------:|---------:|---------:|---------:|-----:|-------:|----------:|
  TestStructReturn |  Clr |     Clr | 17.57 ns | 0.1960 ns | 0.1834 ns | 17.25 ns | 17.89 ns | 17.55 ns |    4 | 0.0127 |      40 B |
   TestClassReturn |  Clr |     Clr | 21.93 ns | 0.4554 ns | 0.5244 ns | 21.17 ns | 23.26 ns | 21.86 ns |    5 | 0.0229 |      72 B |
 TestStructReturn8 |  Clr |     Clr | 38.99 ns | 0.8302 ns | 1.4097 ns | 37.36 ns | 42.35 ns | 38.50 ns |    8 | 0.0127 |      40 B |
  TestClassReturn8 |  Clr |     Clr | 23.69 ns | 0.5373 ns | 0.6987 ns | 22.70 ns | 25.24 ns | 23.37 ns |    6 | 0.0305 |      96 B |
  TestStructReturn | Core |    Core | 12.28 ns | 0.1882 ns | 0.1760 ns | 11.92 ns | 12.57 ns | 12.30 ns |    1 | 0.0127 |      40 B |
   TestClassReturn | Core |    Core | 15.33 ns | 0.4343 ns | 0.4063 ns | 14.83 ns | 16.44 ns | 15.31 ns |    2 | 0.0229 |      72 B |
 TestStructReturn8 | Core |    Core | 34.11 ns | 0.7089 ns | 1.4954 ns | 31.52 ns | 36.81 ns | 34.03 ns |    7 | 0.0127 |      40 B |
  TestClassReturn8 | Core |    Core | 17.04 ns | 0.2299 ns | 0.2150 ns | 16.68 ns | 17.41 ns | 16.98 ns |    3 | 0.0305 |      96 B |

코드 테스트:

using System;
using System.Text;
using System.Collections.Generic;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Attributes.Columns;
using BenchmarkDotNet.Attributes.Exporters;
using BenchmarkDotNet.Attributes.Jobs;
using DashboardCode.Routines.Json;

namespace Benchmark
{
    //[Config(typeof(MyManualConfig))]
    [RankColumn, MinColumn, MaxColumn, StdDevColumn, MedianColumn]
    [ClrJob, CoreJob]
    [HtmlExporter, MarkdownExporter]
    [MemoryDiagnoser]
    public class BenchmarkStructOrClass
    {
        static TestStruct testStruct = new TestStruct();
        static TestClass testClass = new TestClass();
        static TestStruct8 testStruct8 = new TestStruct8();
        static TestClass8 testClass8 = new TestClass8();
        [Benchmark]
        public void TestStructReturn()
        {
            testStruct.TestMethod();
        }

        [Benchmark]
        public void TestClassReturn()
        {
            testClass.TestMethod();
        }


        [Benchmark]
        public void TestStructReturn8()
        {
            testStruct8.TestMethod();
        }

        [Benchmark]
        public void TestClassReturn8()
        {
            testClass8.TestMethod();
        }

        public class TestStruct
        {
            public int Number = 5;
            public struct StructType<T>
            {
                public T Instance;
                public List<string> List;
            }

            public int TestMethod()
            {
                var s = Method1(1);
                return s.Instance;
            }

            private StructType<int> Method1(int i)
            {
                return Method2(++i);
            }

            private StructType<int> Method2(int i)
            {
                return Method3(++i);
            }

            private StructType<int> Method3(int i)
            {
                return Method4(++i);
            }

            private StructType<int> Method4(int i)
            {
                var x = new StructType<int>();
                x.List = new List<string>();
                x.Instance = ++i;
                return x;
            }
        }

        public class TestClass
        {
            public int Number = 5;
            public class ClassType<T>
            {
                public T Instance;
                public List<string> List;
            }

            public int TestMethod()
            {
                var s = Method1(1);
                return s.Instance;
            }

            private ClassType<int> Method1(int i)
            {
                return Method2(++i);
            }

            private ClassType<int> Method2(int i)
            {
                return Method3(++i);
            }

            private ClassType<int> Method3(int i)
            {
                return Method4(++i);
            }

            private ClassType<int> Method4(int i)
            {
                var x = new ClassType<int>();
                x.List = new List<string>();
                x.Instance = ++i;
                return x;
            }
        }

        public class TestStruct8
        {
            public int Number = 5;
            public struct StructType<T>
            {
                public T Instance1;
                public T Instance2;
                public T Instance3;
                public T Instance4;
                public T Instance5;
                public T Instance6;
                public T Instance7;
                public List<string> List;
            }

            public int TestMethod()
            {
                var s = Method1(1);
                return s.Instance1;
            }

            private StructType<int> Method1(int i)
            {
                return Method2(++i);
            }

            private StructType<int> Method2(int i)
            {
                return Method3(++i);
            }

            private StructType<int> Method3(int i)
            {
                return Method4(++i);
            }

            private StructType<int> Method4(int i)
            {
                var x = new StructType<int>();
                x.List = new List<string>();
                x.Instance1 = ++i;
                return x;
            }
        }

        public class TestClass8
        {
            public int Number = 5;
            public class ClassType<T>
            {
                public T Instance1;
                public T Instance2;
                public T Instance3;
                public T Instance4;
                public T Instance5;
                public T Instance6;
                public T Instance7;
                public List<string> List;
            }

            public int TestMethod()
            {
                var s = Method1(1);
                return s.Instance1;
            }

            private ClassType<int> Method1(int i)
            {
                return Method2(++i);
            }

            private ClassType<int> Method2(int i)
            {
                return Method3(++i);
            }

            private ClassType<int> Method3(int i)
            {
                return Method4(++i);
            }

            private ClassType<int> Method4(int i)
            {
                var x = new ClassType<int>();
                x.List = new List<string>();
                x.Instance1 = ++i;
                return x;
            }
        }
    }
}

구조체가 실제 값입니다. 구조체는 비어 있을 수 있지만 null일 수 없습니다.

그러나 .NET 2의 구조는 Nullable 버전을 지원하고 C#은 사용하기 쉽게 하기 위해 일부 구문 설탕을 제공합니다.

int? value = null;
value  = 1;

기본 값 유형 또는 구조체 유형의 모든 변수 또는 필드에는 모든 필드(공개 및 비공개)를 포함하여 해당 유형의 고유 인스턴스가 포함됩니다.대조적으로, 변수 또는 참조 유형 필드는 null을 보유하거나 다른 곳에 저장된 개체를 참조할 수 있으며, 이 개체는 다른 여러 참조도 존재할 수 있습니다.구조체의 필드는 스택에 있거나 다른 힙 개체의 일부일 수 있는 해당 구조체 유형의 필드 또는 변수와 동일한 위치에 저장됩니다.

기본값 유형의 변수 또는 필드를 만들면 기본값으로 작성되고, 구조물 유형의 변수 또는 필드를 만들면 새 인스턴스가 작성되어 모든 필드가 기본값으로 작성됩니다.참조 유형의 인스턴스를 만드는 작업은 기본 방식으로 모든 필드를 만든 다음 유형에 따라 선택적인 추가 코드를 실행하는 것으로 시작됩니다.

원시 유형의 한 변수 또는 필드를 다른 변수로 복사하면 값이 복사됩니다.구조물 유형의 필드 또는 변수를 다른 변수에 복사하면 이전 인스턴스의 모든 필드(공개 및 비공개)가 후자 인스턴스에 복사됩니다.한 변수 또는 참조 유형의 필드를 다른 변수에 복사하면 후자가 전자와 동일한 인스턴스(있는 경우)를 참조하게 됩니다.

C++와 같은 일부 언어에서 유형의 의미론적 동작은 저장 방법과 무관하지만 .NET에서는 그렇지 않습니다.유형이 가변 값 의미론을 구현하는 경우 해당 유형의 한 변수를 다른 인스턴스로 복사하면 첫 번째 변수의 속성이 두 번째 인스턴스에서 참조되는 다른 인스턴스로 복사되고 두 번째 인스턴스의 멤버를 사용하여 두 번째 인스턴스가 변경되지만 첫 번째 인스턴스는 변경되지 않습니다.만약 어떤 유형이 변형 가능한 참조 의미론을 구현한다면, 한 변수를 다른 변수로 복사하고 두 번째 변수의 멤버를 사용하여 객체를 변형시키는 것은 첫 번째 변수에 의해 언급된 객체에 영향을 미칠 것입니다.따라서 복사가 새 인스턴스를 생성하는지 아니면 첫 번째 인스턴스에 대한 다른 참조를 생성하는지는 의미론적으로 중요하지 않습니다.

.NET에서 값 유형은 모든 필드에서 동일한 작업을 수행할 수 있는 경우 위의 의미론 중 하나를 구현할 수 있습니다.그러나 참조 유형은 가변 참조 의미론 또는 불변 의미론만 구현할 수 있습니다. 가변 참조 유형 필드가 있는 값 유형은 가변 참조 의미론 또는 이상한 하이브리드 의미론을 구현하는 것으로 제한됩니다.

언급URL : https://stackoverflow.com/questions/13049/whats-the-difference-between-struct-and-class-in-net

반응형