[C#] 공분산과 공분산이 값 유형을 지원하지 않는 이유

IEnumerable<T>공동 변종 하지만 값 유형, 단지에만 참조 형식을 지원하지 않습니다. 아래 간단한 코드가 성공적으로 컴파일되었습니다.

IEnumerable<string> strList = new List<string>();
IEnumerable<object> objList = strList;

그러나에서 string로 변경 int하면 컴파일 오류가 발생합니다.

IEnumerable<int> intList = new List<int>();
IEnumerable<object> objList = intList;

그 이유는 MSDN에 설명되어 있습니다 .

차이는 참조 유형에만 적용됩니다. 변형 유형 매개 변수에 값 유형을 지정하면 해당 유형 매개 변수는 생성 된 생성 유형에 대해 변하지 않습니다.

나는 그 이유가 가치 유형과 참조 유형 사이의 권투 라고 언급 한 일부 질문을 검색하고 발견했습니다 . 그러나 왜 권투가 그 이유인지에 대해 많은 생각이 들지 않습니다.

공분산과 공분산이 왜 가치 유형을 지원하지 않는지, 그리고 권투가 어떻게 영향을 미치는지 간단하고 자세한 설명을 해 줄 수 있습니까?



답변

기본적으로 차이는 CLR 이 값을 표현 적으로 변경할 필요가 없음을 보장 할 수있는 경우에 적용됩니다 . 참조는 모두 동일하게 보이므로 표시를 변경하지 않고 IEnumerable<string>로 사용할 수 있습니다 IEnumerable<object>. 기본 코드 자체는 인프라가 확실히 유효하다는 것을 보장하는 한 값으로 수행중인 작업을 전혀 알 필요가 없습니다.

값 유형의 경우, 작동하지 않는 – 치료 IEnumerable<int>가 AS를 IEnumerable<object>, 순서를 사용하여 코드는 권투 변환을 수행할지 여부를 알고 있어야합니다.

일반적으로이 주제에 대한 자세한 내용 Eric Lippert의 블로그 표현 및 정체성에 대한 내용을 참조하십시오.

편집 : Eric의 블로그 게시물을 다시 읽은 후에 는 둘이 연결되어 있지만 적어도 표현만큼 정체성 에 관한 것 입니다. 특히:

인터페이스 및 델리게이트 유형의 공변량 및 반 변형 변환에서 모든 가변 유형 인수가 참조 유형이어야합니다. 변형 참조 변환이 항상 ID 보존인지 확인하려면 형식 인수와 관련된 모든 변환도 ID 보존이어야합니다. 형식 인수에 대한 사소하지 않은 모든 변환이 ID 보존임을 확인하는 가장 쉬운 방법은 참조 변환으로 제한하는 것입니다.


답변

기본 표현에 대해 생각하면 이해하기가 더 쉽습니다 (실제로 구현 세부 사항 임에도 불구하고). 다음은 문자열 모음입니다.

IEnumerable<string> strings = new[] { "A", "B", "C" };

strings다음을 나타내는 것으로 생각할 수 있습니다 .

[0] : 문자열 참조-> "A"
[1] : 문자열 참조-> "B"
[2] : 문자열 참조-> "C"

세 가지 요소의 모음으로, 각각은 문자열에 대한 참조입니다. 이것을 객체 컬렉션으로 캐스트 할 수 있습니다.

IEnumerable<object> objects = (IEnumerable<object>) strings;

기본적으로 참조는 객체 참조라는 점을 제외하면 동일한 표현입니다.

[0] : 객체 참조-> "A"
[1] : 객체 참조-> "B"
[2] : 객체 참조-> "C"

표현은 동일합니다. 참고 문헌은 다르게 취급됩니다. 더 이상 string.Length속성에 액세스 할 수 없지만 여전히 전화를 걸 수 있습니다 object.GetHashCode(). 이것을 int 모음과 비교하십시오.

IEnumerable<int> ints = new[] { 1, 2, 3 };
[0] : int = 1
[1] : int = 2
[2] : int = 3

이것을 IEnumerable<object>int로 변환하여 데이터 를 변환해야합니다 .

[0] : 객체 참조-> 1
[1] : 객체 참조-> 2
[2] : 객체 참조-> 3

이 변환에는 캐스트 이상이 필요합니다.


답변

나는 모든 것이 LSP(Liskov Substitution Principle)의 정의에서 시작한다고 생각합니다 .

q (x)가 유형 T의 객체 x에 대해 가능한 속성 인 경우, S (T)의 하위 유형 인 S 유형의 객체 y에 대해 q (y)는 true 여야합니다.

그러나 예를 들어 값 유형 intobject에서를 대신 할 수 없습니다 C#. 증명은 매우 간단합니다.

int myInt = new int();
object obj1 = myInt ;
object obj2 = myInt ;
return ReferenceEquals(obj1, obj2);

객체에 동일한 “참조”를 false할당하더라도 반환 됩니다 .


답변

구현 세부 사항으로 귀결됩니다. 값 유형은 참조 유형과 다르게 구현됩니다.

값 유형을 참조 유형으로 처리하도록 강요하면 (예 : 인터페이스를 통해 참조 유형으로 상자에 표시) 분산을 얻을 수 있습니다.

차이점을 보는 가장 쉬운 방법은 단순히 다음을 고려하는 것입니다 Array. Value 유형의 배열은 연속적으로 (직접적으로) 메모리에 결합됩니다. 여기서 Reference 유형의 배열은 메모리에서 연속적으로 참조 (포인터) 만 갖습니다. 가리키는 객체는 별도로 할당됩니다.

다른 (관련) 문제 (*)는 (거의) 모든 참조 유형이 분산 목적으로 동일한 표현을 가지고 있으며 많은 코드가 유형 간의 차이점을 알 필요가 없으므로 공분산 및 역 분산이 가능하고 쉽게 종종 추가 유형 검사를 생략하여 구현됩니다.

(*) 같은 문제인 것 같습니다 …


답변