C #에서 구조체와 클래스가 별도의 개념 인 이유는 무엇입니까?


44

C #으로 프로그래밍하는 동안 나는 이해할 수없는 이상한 언어 디자인 결정을 발견했습니다.

따라서 C # (및 CLR)에는 struct(스택에 저장된 값 유형, 상속 없음) 및 class(힙에 저장된 참조 유형 유형)의 두 가지 집계 데이터 유형이 있습니다 .

이 설정은 처음에는 훌륭하게 들리지만 집계 유형의 매개 변수를 사용하는 메소드를 우연히 발견하고 실제로 유형 유형 또는 참조 유형인지 확인하려면 해당 유형의 선언을 찾아야합니다. 때때로 혼란 스러울 수 있습니다.

문제에 대한 일반적으로 받아 들여진 해결책은 모든 실수를 가능한 실수를 방지하고 유용성을 제한하기 위해 struct"불변"으로 선언하는 것 같습니다 (필드를로 설정 readonly) struct.

예를 들어 C ++은 훨씬 더 유용한 모델을 사용합니다. 즉, 스택이나 힙에 객체 인스턴스를 만들고 값이나 참조 (또는 포인터)로 전달할 수 있습니다. 나는 C #이 C ++에서 영감을 받았다는 것을 계속 듣고 있으며 왜이 기술을 사용하지 않았는지 이해할 수 없습니다. 두 개의 다른 할당 옵션 (힙 및 스택)을 하나의 구성으로 결합 class하고 struct값으로 또는 명시 적으로 refand out키워드 를 통한 참조로 전달하는 것이 좋습니다.

질문은, 왜 않았다되어 classstruct별도의 C #의 개념 대신 두 개의 할당 옵션을 하나 개의 집합 형태의 CLR이 될?


34
"문제에 대한 일반적으로 받아 들여진 해결책은 모든 구조체를"불변 "으로 선언하는 것 같습니다 ... 구조체의 유용성을 제한하는 것"많은 사람들이 불변 을 만드는 것은 일반적으로 성능 병목 현상의 원인이 아닐 때마다 더 유용 하다고 주장합니다 . 또한 structs가 항상 스택에 저장되는 것은 아닙니다. struct필드가 있는 객체를 고려하십시오 . 메이슨 휠러가 언급했듯이 슬라이싱 문제가 아마도 가장 큰 이유 일 것입니다.
Doval

7
C #이 C ++에서 영감을 얻은 것은 사실이 아닙니다. 오히려 C #은 C ++과 Java의 디자인에서 (당시 잘 들리고 좋은 소리) 모든 실수에서 영감을 얻었습니다.
Pieter Geerkens

18
참고 : 스택 및 힙은 구현 세부 사항입니다. 구조체 인스턴스를 스택에 할당하고 클래스 인스턴스를 힙에 할당해야한다는 것은 없습니다. 그리고 사실조차도 사실이 아닙니다. 예를 들어, 컴파일러가 변수가 로컬 범위를 이스케이프 할 수없는 것으로 이스케이프 분석을 사용하여 클래스 인스턴스 인 경우에도 스택에 할당 할 수 있다고 판단 할 수 있습니다. 심지어 스택이나 힙이 있어야한다고 말하는 것은 아닙니다. 힙에 링크 된 목록으로 환경 프레임을 할당 할 수 있으며 스택도 전혀 가질 수 없습니다.
Jörg W Mittag


3
"그런 다음 집계 유형의 매개 변수를 사용하는 메서드를 우연히 발견하고 실제로 값 유형 또는 참조 유형인지 확인하려면 해당 유형의 선언을 찾아야합니다." ? 이 방법은 값을 전달하도록 요청합니다. 당신은 가치를 전달합니다. 어느 시점에서 참조 유형인지 값 유형인지에 대해 신경 써야합니까?
Luaan

답변:


58

C # (및 Java 및 본질적으로 C ++ 이후에 개발 된 다른 모든 OO 언어) 이이 측면에서 C ++의 모델을 복사하지 않은 이유 는 C ++이 수행하는 방식이 끔찍한 혼란 이기 때문 입니다.

위의 관련 사항을 올바르게 식별했습니다. struct: 값 유형, 상속 없음. class: 참조 유형, 상속이 있습니다. 상속과 가치 유형 (보다 구체적으로, 다형성과 가치 별)은 혼합되지 않습니다. type의 객체를 type Derived의 메소드 인수에 전달한 Base다음 가상 메소드를 호출하는 경우 올바른 동작을 얻는 유일한 방법은 전달 된 항목이 참조인지 확인하는 것입니다.

그와 C ++에서 상속 가능한 객체를 값 유형 (복사 생성자와 객체 슬라이싱 이 생각납니다!)으로 가져 와서 다른 모든 혼란 사이 에서 가장 좋은 해결책은 Just Say No입니다.

훌륭한 언어 디자인은 단지 기능을 구현하는 것이 아니라 어떤 기능을 구현하지 않는지 알고 있으며,이를 수행하는 가장 좋은 방법 중 하나는 당신 앞에 온 사람들의 실수로부터 배우는 것입니다.


29
이것은 C ++에 대한 또 다른 무의미한 주관적 인 파문입니다. 공감할 수는 없지만, 가능하다면 가능합니다.
Bartek Banachewicz

17
@MasonWheeler : " 끔찍한 엉망입니다 "충분히 주관적인 소리. 이것은 이미 당신의 다른 답변 에 대한 긴 논평 스레드에서 논의되었습니다 ; 실이 눌려졌습니다 (불행히도 화염 전쟁 소스에도 유용한 주석이 포함되어 있기 때문에). 나는 여기서 모든 것을 반복 할 가치가 있다고 생각하지 않지만 "C #이 올바르게하고 C ++에서 잘못했다"(전달하려는 메시지 인 것)는 주관적인 진술입니다.
Andy Prowl

15
@MasonWheeler : 나체 된 스레드에서 다른 여러 사람들도 그렇게 했으므로 삭제 된 것이 불행하다고 생각합니다. 나는 그 스레드를 여기에 복제하는 것이 좋지 않다고 생각하지만 짧은 버전은 C ++에서 디자이너 가 아닌 형식 의 사용자 가 형식을 사용해야하는 의미 (참조 의미 또는 값)를 결정합니다. 의미론). 여기에는 장단점이 있습니다. 전문가를 고려 (또는 알지 못함)하지 않고 단점에 대해 분노합니다. 그것이 분석이 주관적인 이유입니다.
Andy Prowl

7
한숨 LSP 위반 논의가 이미 진행된 것으로 생각합니다. 그리고 나는 사람들의 대다수가 LSP 언급이 꽤 이상하고 관련이 없다고 동의했지만 mod가 주석 스레드를 손상 시켰기 때문에 확인할 수는 없다고 생각 합니다 .
Bartek Banachewicz

9
마지막 단락을 맨 위로 이동하고 현재 첫 번째 단락을 삭제하면 완벽한 주장이 있다고 생각합니다. 그러나 현재 첫 단락은 주관적입니다.
Martin York

19

비 유적으로 C #은 기본적으로 펜치와 조절 가능한 렌치를 피해야한다는 것을 누군가가 읽은 조절 도구와 비슷하므로 조절 가능한 렌치가 전혀 포함되어 있지 않으며 펜치는 "안전하지 않은"이라고 표시된 특수 서랍에 잠겨 있습니다. , 고용주가 건강에 대한 책임을지지 않는 면책 조항에 서명 한 후 감독자의 승인을 받아야만 사용할 수 있습니다.

C ++에는 조절 가능한 렌치와 펜치뿐만 아니라 목적이 분명하지 않은 홀수 볼 특수 목적 도구가 포함되어 있으며 올바른 고정 방법을 모르면 쉽게 절단 할 수 있습니다. thumb (하지만 사용 방법을 이해하면 C # 도구 상자의 기본 도구로는 불가능한 작업을 수행 할 수 있음). 또한 선반, 밀링 머신, 표면 그라인더, 금속 절단 띠톱 등을 사용하여 필요할 때마다 완전히 새로운 도구를 설계하고 만들 수 있습니다 (그러나 기계공의 도구는 자신이하고있는 일을 모르거나 부주의 한 경우에도 심각한 부상을 입을 수 있습니다.

이는 철학의 기본 차이점을 반영합니다. C ++은 기본적으로 원하는 디자인에 필요한 모든 도구를 제공하려고합니다. 이 도구를 사용하는 방법을 거의 제어하지 않으므로 드문 상황에서만 잘 작동하는 디자인과 어설픈 아이디어 일뿐 아니라 아무도 모르는 상황을 디자인하는 데 쉽게 사용할 수 있습니다. 그들은 잘 작동 할 것 같습니다. 특히, 실제로는 거의 항상 결합 된 결정을 포함하여 설계 결정을 분리하여 많은 부분을 수행합니다. 결과적으로 C ++ 만 작성하는 것과 C ++을 잘 작성하는 것에는 큰 차이가 있습니다. C ++을 잘 작성하려면 많은 숙어와 경험 법칙을 알아야합니다 (다른 경험 법칙을 어기 전에 얼마나 심각하게 생각해야하는지에 대한 경험 법칙 포함). 결과적으로 C ++는 배우기 쉬움보다 전문가가 사용하기 쉬워졌습니다. 또한 너무 사용하기 어려운 환경도 있습니다.

C # (또는 적어도 힘을 시도하는 더 많은 않습니다 매우 강력하게 제안) 언어 디자이너는 좋은 디자인 관행을 고려 것을. C ++에서 분리 된 (그러나 일반적으로 실제로 함께 사용되는) 상당히 많은 것들이 C #에서 직접 연결됩니다. 그것은 "안전하지 않은"코드가 경계를 약간 밀어 낼 수있게 해주지 만, 정직하게는 전체가 아닙니다.

결과적으로 C #으로 표현하기에는 상당히 어색한 C ++로 직접 표현할 수있는 디자인이 꽤 있습니다. 반면에 C #을 배우는 것이 훨씬 쉬우 며 상황에 따라 작동하지 않는 정말 끔찍한 디자인을 만들 가능성이 크게 줄어 듭니다. 대부분의 경우 (아마도 대부분의 경우) 간단히 "흐름에 따라 이동"하여 견고하고 실행 가능한 디자인을 얻을 수 있습니다. 또는 내 친구 중 하나 (적어도 나는 그를 친구로 생각하고 싶습니다-정말로 동의하는지 확실하지 않음)가 그것을 좋아하는 것처럼 C #을 사용하면 성공의 구덩이에 빠지기 쉽습니다.

따라서 두 언어로 된 방법 class과 방법에 대한 질문을 좀 더 구체적으로 살펴보면 struct상속 계층 구조에서 생성 된 객체를 사용하여 기본 클래스 / 인터페이스의 형태로 파생 클래스의 객체를 사용할 수 있습니다. 구체적인 수준에서 일반적으로 일종의 포인터 또는 참조를 통해 그렇게해야한다는 사실에 붙어 있습니다. 파생 클래스의 객체에는 기본 클래스 / 인스턴스의 인스턴스로 처리 할 수있는 메모리가 포함되어 있습니다. 인터페이스, 파생 된 개체는 해당 메모리 부분의 주소를 통해 조작됩니다.

C ++에서 올바르게 수행하는 것은 프로그래머의 몫입니다. 상속을 사용하는 경우 (예를 들어) 계층 구조에서 다형성 클래스와 작동하는 함수가 포인터 또는 기본에 대한 참조를 통해 그렇게하는지 확인해야합니다. 수업.

C #에서 유형간에 근본적으로 동일한 구분은 훨씬 더 명확하며 언어 자체에 의해 시행됩니다. 프로그래머는 참조로 클래스의 인스턴스를 전달하기 위해 어떤 단계를 수행 할 필요가 없습니다. 기본적으로 발생하기 때문입니다.


2
C ++ 팬으로서, 이것은 C #과 스위스 육군 전기 톱의 차이점에 대한 훌륭한 요약이라고 생각합니다.
David Thornley

1
@DavidThornley : 나는 적어도 균형 잡힌 비교라고 생각하는 것을 쓰려고했습니다. 손가락을 가리 키지 말고 이것을 썼을 때 내가 본 것 중 일부는 (정확하게 말해서) 다소 부정확 한 것으로 나를 감동시켰다.
Jerry Coffin

7

이것은에서이다 "C 번호 : 왜합니까 우리는 또 다른 언어가 필요하십니까?" -거너 슨, 에릭 :

단순성은 C #의 중요한 디자인 목표였습니다.

단순성과 언어의 순도를 넘어서는 것이 가능하지만 순도를위한 순도는 전문 프로그래머에게는 거의 쓸모가 없습니다. 따라서 우리는 프로그래머가 직면 한 실제 문제를 해결하면서 단순하고 간결한 언어를 갖고 자하는 욕구의 균형을 맞추려고 노력했습니다.

[...]

값 유형 , 연산자 오버로드 및 사용자 정의 변환은 모두 언어에 복잡성을 추가 하지만 중요한 사용자 시나리오를 엄청나게 단순화 할 수 있습니다.

객체에 대한 참조 의미론은 많은 문제 (물론 슬라이스뿐만 아니라)를 피할 수있는 방법이지만 실제 문제는 때때로 의미 론적 가치를 가진 객체를 요구할 수 있습니다 (예 : 참조 의미론을 절대 사용해서는 안되는 것처럼 소리를 살펴보십시오) . 다른 관점에서).

따라서 더러워지고, 추악하고, 나쁜 물건과 가치가있는 물체를 태그보다 분리하는 것보다 더 나은 접근 방법은 무엇입니까 struct?


1
나는 더럽고, 추악하고, 나쁜 객체를 참조 시맨틱으로 사용하지 않을 수도 있습니다.
Bartek Banachewicz

어쩌면 ... 나는 길을 잃었 어
manlio 2013

2
Java 디자인에서 가장 큰 결함 중 하나 인 IMHO는 변수가 ID 또는 소유권 을 캡슐화하는 데 사용되는지 여부를 선언 할 수단 이없고 C #에서 가장 큰 결함 중 하나는 작업을 구별 할 수있는 수단이 없다는 것입니다. 변수가 참조를 보유한 오브젝트에 대한 조작의 변수 런타임이 그러한 차이점에 신경 쓰지 않더라도 언어에서 유형의 변수를 int[]공유 가능 또는 변경 가능해야하는지 여부를 지정하면 (배열은 둘 중 하나 일 수 있지만 둘 다 아닐 수 있음) 잘못된 코드가 잘못 표시되는 데 도움이됩니다.
supercat

4

에서 파생 된 값 유형을 생각하는 대신 Object클래스 인스턴스 유형과 완전히 별개의 유니버스에 존재하는 스토리지 위치 유형을 생각하지만 모든 값 유형에 해당하는 힙 객체 유형이있는 것이 더 도움이됩니다. 구조 유형의 저장 위치는 단순히 유형의 공용 및 개인 필드를 연결하며 힙 유형은 다음과 같은 패턴에 따라 자동 생성됩니다.

// Defined structure
struct Point : IEquatable<Point>
{
  public int X,Y;
  public Point(int x, int y) { X=x; Y=y; }
  public bool Equals(Point other) { return X==other.X && y==other.Y; }
  public bool Equals(Object other)
  { return other != null && other.GetType()==typeof(this) && Equals(Point(other)); }
  public bool ToString() { return String.Format("[{0},{1}", x, y); }
  public bool GetHashCode() { return unchecked(x+y*65531); }
}        
// Auto-generated class
class boxed_Point: IEquatable<Point>
{
  public Point value; // Fake name; C++/CLI, though not C#, allow full access
  public boxed_Point(Point v) { value=v; }
  // Members chain to each member of the original
  public bool Equals(Point other) { return value.Equals(other); }
  public bool Equals(Object other) { return value.Equals(other); }
  public String ToString() { return value.ToString(); }
  public Int32 GetHashCode() { return value.GetHashCode(); }
}

그리고 다음과 같은 문장 : Console.WriteLine ( "값은 {0}", somePoint);

다음과 같이 번역됩니다 : boxed_Point box1 = new boxed_Point (somePoint); Console.WriteLine ( "값은 {0}", box1);

실제로 저장소 위치 유형과 힙 인스턴스 유형은 별도의 유니버스에 있으므로 힙 인스턴스 유형을 다음과 같이 호출 할 필요는 없습니다 boxed_Int32. 시스템은 힙 컨텍스트 인스턴스가 필요한 컨텍스트와 저장 위치가 필요한 컨텍스트를 알고 있기 때문입니다.

어떤 사람들은 객체처럼 행동하지 않는 모든 가치 유형을 "악"으로 간주해야한다고 생각합니다. 나는 반대 견해를 취한다. 가치 유형의 저장 위치는 객체도 아니고 객체에 대한 참조도 아니기 때문에 객체처럼 행동해야한다는 기대는 도움이되지 않는 것으로 간주된다. 구조체가 객체처럼 유용하게 작동 할 수있는 경우에는 그렇게하는 데 아무런 문제가 없지만 각 struct테이프의 핵심은 덕트 테이프와 함께 붙어있는 공공 및 민간 분야의 집합에 불과합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.