클래스 대신 구조체를 언제 사용합니까? [닫은]


174

구조체와 클래스를 언제 사용할 지에 대한 경험 규칙은 무엇입니까? 나는 그 용어에 대한 C # 정의를 생각하고 있지만 언어가 비슷한 개념을 가지고 있다면 당신의 의견도 듣고 싶습니다.

나는 거의 모든 것에 클래스를 사용하는 경향이 있으며 무언가가 매우 단순하고 PhoneNumber와 같은 값 유형이어야하는 경우에만 구조체를 사용합니다. 그러나 이것은 비교적 사소한 사용처럼 보이고 더 흥미로운 사용 사례가 있기를 바랍니다.



7
@Frustrated 확실히 관련이 있지만 질문을 다른 사이트에있는 것의 복제물로 표시 할 수 없습니다.
Adam Lear

@Anna Lear : 저는 복제에 근접하지 않도록 이주하기로 결정했습니다. 마이그레이션되면 복제본으로 올바르게 닫을 수 있습니다 .
FrustratedWithFormsDesigner

5
@ 좌절 나는 마이그레이션해야한다고 생각하지 않습니다.
Adam Lear

15
이것은 P.SE 대 SO에 훨씬 적합한 질문처럼 보입니다. 그렇기 때문에 여기에 물었습니다.
RationalGeek

답변:


169

따라야 할 일반적인 규칙은 구조체가 작고 간단한 (1 레벨) 관련된 속성 모음이어야하며, 일단 생성되면 변경할 수 없다는 것입니다. 다른 것을 위해, 수업을 사용하십시오.

C #은 구조체와 클래스가 정의 키워드 이외의 선언에서 명시적인 차이가 없다는 점에서 훌륭합니다. 따라서 구조체를 클래스로 "업그레이드"하거나 반대로 클래스를 구조체로 "다운 그레이드"해야한다고 생각되면 키워드를 변경하는 간단한 문제입니다 (기타 몇 가지 문제가 있습니다. 다른 클래스 또는 구조체 유형에서 기본 매개 변수가없는 생성자를 명시 적으로 정의 할 수 없습니다).

구조체에 대해 알아야 할 더 중요한 것은 값 타입이기 때문에 클래스 (참조 타입)처럼 취급하면 고통을 반으로 줄 수 있다는 것입니다. 특히 구조의 속성을 변경 가능하게하면 예기치 않은 동작이 발생할 수 있습니다.

예를 들어 A와 B라는 두 속성이있는 SimpleClass 클래스가 있다고 가정합니다.이 클래스의 복사본을 인스턴스화하고 A와 B를 초기화 한 다음 인스턴스를 다른 메서드에 전달합니다. 이 메서드는 A와 B를 추가로 수정합니다. 호출 함수 (인스턴스를 만든 함수)로 돌아 가면 인스턴스의 A와 B는 호출 된 메서드에 의해 주어진 값을 갖게됩니다.

자, 당신은 그것을 구조체로 만듭니다. 속성은 여전히 ​​변경 가능합니다. 이전과 동일한 구문으로 동일한 작업을 수행하지만 이제는 메서드를 호출 한 후 A 및 B의 새 값이 인스턴스에 없습니다. 어떻게 된 거예요? 글쎄, 당신의 클래스는 이제 구조체입니다. 즉, 값 유형입니다. 값 유형을 메소드에 전달하는 경우, 기본값 (out 또는 ref 키워드없이)은 "값별"을 전달하는 것입니다. 인스턴스의 얕은 사본은 메소드에서 사용하기 위해 작성된 다음 메소드가 완료되면 초기 인스턴스는 그대로두고 폐기됩니다.

구조체의 멤버로 참조 유형을 가지고 있다면 이것은 더욱 혼란스러워집니다 ( 거의 모든 경우에 허용되지 않지만 매우 나쁜 연습). 클래스는 복제되지 않으며 (구조체에 대한 구조체 참조 만 해당) 구조체에 대한 변경은 원래 객체에 영향을 미치지 않지만 구조체의 하위 클래스에 대한 변경은 호출 코드의 인스턴스에 영향을 미칩니다. 이렇게하면 변경 가능한 구조체를 매우 일관되지 않은 상태로 만들어 실제 문제가있는 곳에서 멀리 떨어진 곳에서 오류를 일으킬 수 있습니다.

이러한 이유로 C #의 거의 모든 기관은 항상 구조를 변경할 수 없다고 말합니다. 소비자는 객체 구성시에만 속성 값을 지정할 수 있으며 해당 인스턴스 값을 변경할 수있는 수단을 제공하지 않습니다. 읽기 전용 필드 또는 가져 오기 전용 속성이 규칙입니다. 소비자가 값을 변경하려는 경우 원하는 변경 사항을 사용하여 이전 값을 기반으로 새 개체를 만들거나 동일한 방법으로 메서드를 호출 할 수 있습니다. 이것은 당신의 구조체의 하나의 인스턴스를 하나의 개념적 "가치"로, 불가분의, 그리고 다른 모든 것과는 구별 할 수있는 (그러나 아마도 동일 할 수있는) 하나의 개념적 "가치"로 취급하도록합니다. 유형별로 저장된 "값"에 대해 작업을 수행하면 초기 값과 다른 새로운 "값"을 얻습니다.

좋은 예를 보려면 DateTime 유형을보십시오. DateTime 인스턴스의 필드를 직접 지정할 수 없습니다. 새 인스턴스를 작성하거나 기존 인스턴스에서 새 인스턴스를 생성하는 메소드를 호출해야합니다. 날짜와 시간이 숫자 5와 같이 "값"이고 숫자 5를 변경하면 5가 아닌 새 값이 생성되기 때문입니다. 5 + 1 = 6이 5가 6이라는 의미는 아니기 때문에 1을 추가했기 때문에. DateTimes도 같은 방식으로 작동합니다. 12:00은 1 분을 추가하면 12:01이되지 않습니다. 대신 12:00과 다른 새 값 12:01을 얻습니다. 이것이 귀하의 유형에 대한 논리적 인 문제인 경우 (.NET에 내장되어 있지 않은 좋은 개념의 예는 돈, 거리, 무게 및 작업이 가치의 모든 부분을 고려해야하는 UOM의 다른 수량입니다), 그런 다음 구조체를 사용하고 적절하게 디자인하십시오. 객체의 하위 항목을 독립적으로 변경할 수있는 대부분의 경우 클래스를 사용하십시오.


1
좋은 대답입니다! 나는 그것을 '도메인 주도 개발'방법론과 책으로 읽는 것을 지적 할 것이다. 이것은 실제로 구현하려는 개념에 따라 클래스 나 구조를 사용해야하는 이유에 대한 당신의 의견과 다소 관련이있는 것 같다
Maxim Popravko

1
언급 할 가치가있는 약간의 세부 사항 : 구조체에서 자신의 기본 생성자를 정의 할 수 없습니다. 모든 것을 기본값으로 초기화하는 것과 관련이 있습니다. 보통, 그것은 아주 사소합니다. 특히 수업에 좋은 후보자 인 수업에서. 그러나 클래스를 구조체로 변경하면 키워드를 변경하는 것 이상을 의미 할 수 있습니다.
Laurent Bourgault-Roy

1
@ LaurentBourgault-Roy-맞습니다. 다른 문제도 있습니다. 예를 들어 구조체는 상속 계층 구조를 가질 수 없습니다. 인터페이스를 구현할 수는 있지만 System.ValueType 이외의 것 (내재적으로 구조체에 의해)에서 파생 될 수는 없습니다.
KeithS

JavaScript 개발자는 두 가지를 요구해야합니다. 구조체의 일반적인 사용법과 다른 객체 리터럴은 어떻게 다르며 구조체를 변경할 수없는 것이 왜 중요합니까?
Erik Reppen

1
@ErikReppen : 두 질문에 대한 답 : 구조체가 함수에 전달되면 값에 의해 전달됩니다. 클래스를 사용하면 클래스에 대한 참조를 값으로 전달합니다. 이 문제가 왜 에릭 Lippert의는 설명 : blogs.msdn.com/b/ericlippert/archive/2008/05/14/... . KeithS의 마지막 단락에서도 이러한 질문을 다룹니다.
Brian

14

대답은 "순수한 데이터 구조에 구조체를 사용하고 작업이있는 개체에 클래스를 사용하는 것"은 분명히 잘못된 IMO입니다. 구조체에 많은 수의 속성이 있으면 클래스가 거의 항상 더 적합합니다. Microsoft는 종종 효율성 관점에서 유형이 16 바이트보다 큰 경우 클래스 여야한다고 말합니다.

https://stackoverflow.com/questions/1082311/why-should-a-net-struct-be-less-than-16-bytes


1
물론. 내가 할 수 있다면 나는 공감했다. 나는 구조체가 데이터를위한 것이고 객체가 아니라는 아이디어를 어디에서 얻었는지 알 수 없다. C #의 구조체는 스택에 할당하기위한 메커니즘 일뿐입니다. 전체 구조체의 복사본은 객체 포인터의 복사본 대신 전달됩니다.
mike30

2
@mike-C # 구조체는 반드시 스택에 할당 될 필요는 없습니다.
Lee

1
@mike "구조는 데이터를위한 것"이라는 생각은 아마도 수년간의 C 프로그래밍에 의해 얻은 습관에서 비롯된 것입니다. C에는 클래스가 없으며 해당 구조체는 데이터 전용 컨테이너입니다.
Konamiman

분명히 Michael K의 답변을 읽은 C 프로그래머가 적어도 3 명 있습니다. ;-)
bytedev

10

a class와 a 의 가장 중요한 차이점 struct은 다음 상황에서 발생합니다.

  Thing thing1 = 새로운 Thing ();
  thing1.somePropertyOrField = 5;
  일 thing2 = 일 1;
  thing2.somePropertyOrField = 9;

마지막 진술의 효과는 무엇입니까 thing1.somePropertyOrField? 만약 Thing구조체 및 somePropertyOrField노출 공공 필드, 객체가 thing1thing2서로 "분리 된"입니다, 그래서 후자의 문이 영향을 미치지 않습니다 thing1. 경우는 Thing클래스이며, 다음 thing1thing2서로 연결되고, 그래서 후자의 문을 작성합니다 thing1.somePropertyOrField. 전자의 의미가 더 의미가있는 경우에는 구조체를 사용해야하고, 후자의 의미가 더 의미가있는 경우에는 클래스를 사용해야합니다.

어떤 사람들은 어떤 것을 바꾸고 자하는 욕구가 클래스라는 점에서 유리한 주장이라고 조언하지만, 그 반대의 경우는 사실입니다. 만약 어떤 데이터를 보유 할 목적으로 존재하는 어떤 것이 변할 수 있다면, 인스턴스가 어떤 것에 연결되어 있는지 확실하지 않은 경우, 인스턴스가 다른 것에 연결되지 않았 음을 분명히하기 위해 (노출 된 필드가있는) 구조체 여야합니다.

예를 들어, 다음 문장을 고려하십시오.

  Person somePerson = myPeople.GetPerson ( "123-45-6789");
  somePerson.Name = "메리 존슨"; // "메리 스미스"

두 번째 진술서에 저장된 정보가 변경 myPeople됩니까? 경우 Person노출 된 필드 구조체는, 그것은하지 않습니다, 그리고 그것 인 노출 필드 구조체의 명백한 결과가 될 것입니다하지 않을 것이라는 사실 ; Person구조체이고 업데이 트하려는 경우 myPeople, 분명히 뭔가해야합니다 myPeople.UpdatePerson("123-45-6789", somePerson). Person그러나 클래스 인 경우 위의 코드가의 내용을 MyPeople업데이트하지 않거나 항상 업데이트하거나 때로는 업데이트 하는지 여부를 결정하기가 훨씬 어려울 수 있습니다 .

구조체가 "불변"이어야한다는 개념과 관련하여, 나는 일반적으로 동의하지 않습니다. "불변"구조체에 대한 유효한 사용 사례가 있지만 (변형이 생성자에서 시행되는 경우) 변경의 일부가 어색하고 낭비 적이며 단순히 노출하는 것보다 버그를 유발할 수있는 경우 항상 전체 구조체를 다시 작성해야합니다. 직접 필드. 예를 들어, 고려 PhoneNumber, 그 필드 구조체를 다른 사람의 사이에서 포함 AreaCode하고 Exchange, 하나는 가지고 가정 List<PhoneNumber>. 다음의 효과는 매우 분명해야합니다.

  for (int i = 0; i <myList.Count; i ++)
  {
    PhoneNumber theNumber = myList [i];
    if (theNumber.AreaCode == "312")
    {
      문자열 newExchange = "";
      if (new312to708Exchanges.TryGetValue (theNumber.Exchange), newExchange)
      {
        theNumber.AreaCode = "708";
        theNumber.Exchange = newExchange;
        myList [i] = theNumber;
      }
    }
  }

위의 코드 PhoneNumber에서는 AreaCode및 이외의 필드를 아는 것이 없습니다 Exchange. 경우 PhoneNumber소위 "불변"구조체했다, 그것은해야 할 것 중 하나 그것은 제공하는 withXX지정된 필드에 전달 된 값을 개최 새 구조체 인스턴스를 반환, 그렇지 않으면이 필요하다 각 필드에 대한 방법을, 위의 코드는 구조체의 모든 필드에 대해 알 수 있습니다. 정확히 호소력이 없습니다.

BTW에서 구조체가 가변 유형에 대한 참조를 합리적으로 보유 할 수있는 경우가 적어도 두 가지 있습니다.

  1. 구조체의 의미는 객체의 속성을 유지하기위한 지름길이 아니라 해당 객체의 아이덴티티를 저장하고 있음을 나타냅니다. 예를 들어, 'KeyValuePair'는 특정 버튼의 ID를 보유하지만 해당 버튼의 위치, 강조 상태 등에 대한 지속적인 정보를 보유하지는 않습니다.
  2. 구조체는 해당 객체에 대한 유일한 참조를 보유하고 있으며 아무도 참조를 얻지 못하며 해당 객체에 대해 수행 될 모든 돌연변이는 해당 객체에 대한 참조가 어디에나 저장되기 전에 수행되었을 것입니다.

전자의 시나리오에서 객체의 IDENT는 불변이다. 두 번째로, 내포 된 객체의 클래스는 불변성을 강요하지는 않지만 참조를 보유한 구조체는이를 유지합니다.


7

나는 하나의 메소드가 필요할 때만 구조체를 사용하는 경향이 있으므로 클래스를 너무 무겁게 보이게 만듭니다. 따라서 때로는 구조체를 경량 객체로 취급합니다. 주의 : 이것은 항상 작동하는 것은 아니며 항상 최선의 방법은 아니므로 상황에 따라 달라집니다!

추가 자료

더 공식적인 설명을 위해 MSDN은 다음과 같이 말합니다 : http://msdn.microsoft.com/en-us/library/ms229017.aspx 이 링크는 위에서 언급 한 내용을 확인하므로 구조체는 소량의 데이터를 저장하는 데만 사용해야합니다.


5
해당 사이트가 스택 오버플로 인 경우에도 다른 사이트에 대한 질문 은 중복으로 계산되지 않습니다. 다른 장소에 대한 링크뿐만 아니라 실제 답변을 포함하도록 답변을 확장하십시오. 링크가 변경되면 지금 작성된 답변은 쓸모가 없으며 정보를 보존하고 프로그래머를 가능한 한 독립적으로 만들고 싶습니다. 감사!
Adam Lear

2
@Anna Lear 알려 주셔서 감사합니다. 지금부터 명심하십시오!
rarazd

2
-1 "하나의 메소드가 필요할 때만 구조체를 사용하는 경향이있어서 클래스를 너무 무겁게 만드는 것 같습니다." ... 무거워? 그게 무슨 뜻이야? ... 그리고 당신은 정말로 왜냐하면 단지 하나의 방법이 있다면 아마도 구조체 일 것입니다.
bytedev

2

순수한 데이터 구조에는 구조체를 사용하고 연산이있는 객체에는 클래스를 사용하십시오.

데이터 구조에 액세스 제어가 필요없고 get / set 이외의 특수 작업이없는 경우 구조체를 사용하십시오. 이것은 모든 구조가 데이터의 컨테이너라는 것을 분명히합니다.

구조에 더 복잡한 방식으로 구조를 수정하는 작업이있는 경우 클래스를 사용하십시오. 클래스는 메소드에 대한 인수를 통해 다른 클래스와 상호 작용할 수도 있습니다.

그것을 벽돌과 비행기의 차이점으로 생각하십시오. 벽돌은 구조체입니다. 길이, 너비 및 높이가 있습니다. 별로 할 수 없습니다. 비행기는 제어 표면 이동 및 엔진 추력 변경과 같은 여러 가지 조작으로 비행기를 돌연변이시킬 수 있습니다. 수업으로 더 좋을 것입니다.


2
C #에서 "순수한 데이터 구조에는 구조체를 사용하고 연산이있는 개체에는 클래스를 사용하십시오"는 의미가 없습니다. 아래 답변을 참조하십시오.
bytedev

1
"순수한 데이터 구성에는 구조체를 사용하고 작업이있는 객체에는 클래스를 사용하십시오." 이것은 C #이 아닌 C / C ++에 해당됩니다
taktak004
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.