[C#] C #에서 String이 값 유형처럼 동작하는 참조 유형 인 이유는 무엇입니까?

문자열은 불변이고 같은 객체를 참조하지 않고 텍스트를 비교하기 위해 == 오버로드되는 것과 같은 값 유형의 특성을 대부분 가지고 있지만 참조 유형입니다.

그렇다면 왜 문자열이 값 유형이 아닌가?



답변

문자열은 값이 크지 않으므로 힙에 저장해야하기 때문에 값 유형이 아닙니다. 값 유형은 (아직 CLR의 모든 구현에서) 스택에 저장됩니다. 스택 할당 문자열은 모든 종류의 것들을 망칠 것입니다 : 스택은 32 비트의 경우 1MB, 64 비트의 경우 4MB입니다. 각 문자열을 상자에 넣어 복사해야하며 벌금을 내야하며 인턴 문자열을 사용할 수 없으며 메모리 사용이 필요합니다 풍선 등

(편집 : 구현 세부 사항 인 값 유형 스토리지에 대한 설명이 추가되어 System.ValueType에서 상속되지 않는 값 sematics가있는 유형이 있습니다. 감사합니다. 벤)


답변

값 유형 인 경우 성능 (공간 및 시간!)이 끔찍하고 메소드 등에서 전달되거나 리턴 될 때마다 값을 복사해야하므로 값 유형이 아닙니다.

세계를 제정신으로 유지하기위한 가치 의미론이 있습니다. 코드를 작성하는 것이 얼마나 어려운지 상상할 수 있습니까?

string s = "hello";
string t = "hello";
bool b = (s == t);

설정 b으로 false? 모든 응용 프로그램에서 코딩이 얼마나 어려운지를 상상해보십시오.


답변

참조 유형과 값 유형의 구별은 기본적으로 언어 디자인에서 성능 상충 관계입니다. 참조 유형은 힙에 작성되므로 구성 및 소멸 및 가비지 콜렉션에 약간의 오버 헤드가 있습니다. 반면에 값 유형은 전체 객체가 포인터가 아니라 복사되기 때문에 메소드 호출에 대한 오버 헤드가 있습니다 (데이터 크기가 포인터보다 큰 경우). 문자열은 포인터 크기보다 훨씬 클 수 있으며 일반적으로 참조 유형으로 설계되었습니다. 또한 Servy가 지적했듯이 값 유형의 크기는 컴파일 타임에 알고 있어야하며 이는 항상 문자열의 경우는 아닙니다.

가변성의 문제는 별도의 문제입니다. 참조 유형과 값 유형은 모두 변경 가능하거나 변경 불가능할 수 있습니다. 가변 값 형식의 의미가 혼동 될 수 있으므로 일반적으로 값 형식은 변경할 수 없습니다.

참조 유형은 일반적으로 변경 가능하지만 의미가있는 경우 변경 불가능하도록 설계 할 수 있습니다. 문자열은 특정 최적화가 가능하기 때문에 불변으로 정의됩니다. 예를 들어, 동일한 프로그램에서 동일한 문자열 리터럴이 여러 번 발생하는 경우 (일반적인 경우) 컴파일러는 동일한 객체를 재사용 할 수 있습니다.

그렇다면 “==”문자열을 텍스트로 비교하기 위해 오버로드되는 이유는 무엇입니까? 가장 유용한 의미론이기 때문입니다. 두 문자열이 텍스트와 같으면 최적화로 인해 동일한 객체 참조 일 수도 있고 아닐 수도 있습니다. 따라서 참조를 비교하는 것은 무의미하지만 텍스트를 비교하는 것은 거의 항상 원하는 것입니다.

보다 일반적으로 말하면, Strings는 value semantics 라고 불립니다 . 이는 C # 고유의 구현 세부 사항 인 값 형식보다 일반적인 개념입니다. 값 형식에는 값 의미가 있지만 참조 형식에는 값 의미가있을 수도 있습니다. 유형에 값 의미가있는 경우 기본 구현이 참조 유형 또는 값 유형인지 실제로 알 수 없으므로 구현 세부 사항을 고려할 수 있습니다.


답변

이것은 오래된 질문에 대한 늦은 대답이지만 다른 모든 대답에는 요점이 없습니다. 2005 년 .NET 2.0까지 .NET에는 제네릭이 없었습니다.

String때문에 대신 값 형식의 참조 형식은 마이크로 소프트가 그 문자열이 제네릭이 아닌 컬렉션에 가장 효율적인 방법으로 저장 될 수 있도록하는 것이 매우 중요했다 같은, System.Collections.ArrayList.

제네릭이 아닌 컬렉션에 값 형식을 저장하려면 objectboxing이라는 형식으로 특별한 변환이 필요합니다 . CLR은 값 유형을 상자에 넣을 때 값을 a 안에 System.Object넣고 관리되는 힙에 저장합니다.

컬렉션에서 값을 읽으려면 unboxing이라고하는 역 연산이 필요합니다.

권투와 unboxing 모두 무시할 수없는 비용이 있습니다 : 권투는 추가 할당이 필요하고 unboxing은 유형 검사가 필요합니다.

일부 답변 string은 크기가 가변적이므로 값 유형으로 구현 될 수 없다고 잘못 주장 합니다. 실제로 작은 문자열 최적화 전략을 사용하여 문자열을 고정 길이 데이터 구조로 구현하는 것은 쉽습니다. 문자열은 외부 버퍼에 대한 포인터로 저장되는 큰 문자열을 제외하고는 유니 코드 문자 시퀀스로 메모리에 직접 저장됩니다. 두 표현은 동일한 고정 길이, 즉 포인터의 크기를 갖도록 설계 될 수 있습니다.

제네릭이 첫날부터 존재했다면 문자열을 값 유형으로 사용하는 것이 더 간단한 의미, 더 나은 메모리 사용 및 더 나은 캐시 위치를 가진 더 나은 솔루션 일 것입니다. List<string>함유 단지 작은 문자열은 메모리의 하나의 연속 블록 수 있었다.


답변

문자열 만이 변경 불가능한 참조 유형일뿐만 아니라
멀티 캐스트 델리게이트도.
그래서 작성하는 것이 안전합니다

protected void OnMyEventHandler()
{
     delegate handler = this.MyEventHandler;
     if (null != handler)
     {
        handler(this, new EventArgs());
     }
}

문자열을 사용하고 메모리를 할당하는 가장 안전한 방법이기 때문에 문자열을 변경할 수 없다고 가정합니다. 왜 그들은 가치 유형이 아닌가? 이전 작성자는 스택 크기 등에 대해 맞습니다. 또한 프로그램에서 동일한 상수 문자열을 사용할 때 참조 유형을 문자열로 지정하면 어셈블리 크기를 절약 할 수 있습니다. 정의하면

string s1 = "my string";
//some code here
string s2 = "my string";

“my string”상수의 두 인스턴스가 모두 어셈블리에 한 번만 할당 될 수 있습니다.

일반적인 참조 유형과 같은 문자열을 관리하려면 문자열을 새 StringBuilder (string s) 안에 넣습니다. 또는 MemoryStreams를 사용하십시오.

함수에 거대한 문자열이 전달 될 것으로 예상되는 라이브러리를 작성하려면 매개 변수를 StringBuilder 또는 Stream으로 정의하십시오.


답변

또한 문자열이 구현되는 방식 (플랫폼마다 다름)과 함께 스티칭을 시작할 때. 를 사용하는 것과 같습니다 StringBuilder. 그것은 당신이 끝에 도달하면, 당신이 큰 연결 성능을 수행하는 경우 큰 연결 성능을 방해하지 않기를 바랍니다, 당신을 위해 더 많은 메모리를 할당하기 위해 버퍼를 할당합니다.

Jon Skeet이 여기서 도울 수 있을까요?


답변

주로 성능 문제입니다.

문자열이 LIKE 값 유형으로 작동하면 코드를 작성할 때 도움이되지만 값 유형이되면 성능이 크게 저하됩니다.

심층적 인 모양을 원하면 .net 프레임 워크의 문자열에 대한 멋진 기사 를 살펴보십시오 .