2012-03-13 5 views
27

마지막 줄이 허용되지 않는 이유는 무엇입니까?IEnumerable <struct>을 IEnumerable <object>으로 캐스팅 할 수없는 이유는 무엇입니까?

double은 객체에서 파생되지 않는 값 유형이므로 공분산이 작동하지 않습니까?

가이 일 할 수있는 방법이 없다는 것을 뜻 :

public interface IMyInterface<out T> 
{ 
    string Method(); 
} 

public class MyClass<U> : IMyInterface<U> 
{ 
    public string Method() 
    { 
     return "test"; 
    } 
} 

public class Test 
{ 
    public static object test2() 
    { 
     IMyInterface<double> a = new MyClass<double>(); 
     IMyInterface<object> b = a; // Invalid cast! 
     return b.Method(); 
    } 
} 

을 내가 쓸 필요가 나의 IMyInterface<T>.Cast<U>()을 자신의 것을 할까?

+0

[공분산과 반항성이 값 유형을 지원하지 않는 이유]의 복제본 (http://stackoverflow.com/questions/12454794/why-covariance-and-contravariance-do-not-support-value-type) – nawfal

답변

44

왜 마지막 줄이 허용되지 않습니까?

double은 값 유형이고 객체는 참조 유형이므로, 공분산은 두 유형 모두 참조 유형 인 경우에만 작동합니다.

double은 객체에서 파생되지 않는 값 유형이므로 공분산이 작동하지 않습니까?

No.는 개체에서 파생됩니다. 모든 값 유형은 객체에서 파생됩니다.

이제 질문 당신이 물어 봤어야 :

왜 공분산이 IEnumerable<object>IEnumerable<double>를 변환 작동하지 않는 이유는 무엇입니까?

누가 복싱을합니까? double에서 object 로의 변환은 상자 double이어야합니다. 실제로 IEnumerator<double>.Current 구현에 대한 호출 인 IEnumerator<object>.Current에 대한 호출이 있다고 가정합니다. 호출자는 객체가 반환 될 것으로 기대합니다. 피 호출자가 두 배를 반환합니다. IEnumerator<double>.Current에 의해 반환 된 double을 boxed double로 바꾸는 복싱 명령을 수행하는 코드는 어디에 있습니까?

아무데도인데, 이것이이 변환이 불법 인 이유입니다. 아무데도 없습니다. Current에 대한 호출은 평가 스택에 8 바이트 더블을 넣을 것이고 소비자는 평가 스택에서 박스형 더블에 대한 4 바이트 참조를 기대할 것이기 때문에 소비자는 크래시하고 끔찍하게 죽을 것입니다 잘못 정렬 된 스택과 유효하지 않은 메모리에 대한 참조가 있습니다.

당신이 에 상자 다음은 어떤 시점에서을 쓸 수 있고, 당신이 그것을 쓰는 얻는 사람이야 실행 코드를합니다.

IEnumerable<object> objects2 = doubleenumerable.Cast<object>(); 

지금 당신이 참조 이중 8 바이트에서 이중 변환 복싱 명령을 포함하는 도우미 메서드를 호출하는 가장 쉬운 방법은 Cast<T> 확장 방법을 사용하는 것입니다.

업데이트 : 의견 작성자는 내가 원래 질문에 대한 해결책만큼 어려운 모든 문제를 해결하는 메커니즘의 존재를 전제로하여 질문에 답변했습니다. Cast<T>의 구현은 박스 여부를 아는 문제를 어떻게 해결할 수 있습니까?

이 스케치와 같습니다. 매개 변수 유형은 하지 일반적인 참고 :

public static IEnumerable<T> Cast<T>(this IEnumerable sequence) 
{ 
    if (sequence == null) throw ... 
    if (sequence is IEnumerable<T>) 
     return sequence as IEnumerable<T>; 
    return ReallyCast<T>(sequence); 
} 

private static IEnumerable<T> ReallyCast<T>(IEnumerable sequence) 
{ 
    foreach(object item in sequence) 
     yield return (T)item; 
} 

T에 객체의 캐스트가 언 박싱 변환하거나 참조 변환이 런타임에 연기 여부를 결정하기위한 책임을. 지터는 T가 참조 유형인지 또는 값 유형인지 여부를 알고 있습니다. 99 %의 시간은 참조 유형이 될 것입니다.

+6

꺼짐 주제지만, 당신이 트위터를 보내길 바래요. – Joe

+1

1+ 게시 속도가 더 빠릅니다. 나는 "소비자가 잘못 정렬 된 스택과 유효하지 않은 메모리에 대한 참조로 인해 끔찍하게 충돌하고 죽을 것"을 좋아합니다. –

+17

@JoeTuskan : 내가 120 자까지 들어 맞는 말을하고 일반적으로 관심이있을 때, 나는 그렇게 할 것이다. 오래 기다리십시오. –

4

허용되는 항목과 허용되지 않는 항목을 이해하고 해당 항목이 그대로 작동하는 이유를 이해하려면 두꺼운 부분에서 무슨 일이 벌어지고 있는지 이해하는 것이 좋습니다. 모든 값 유형에 해당하는 유형의 클래스 객체가 있으며 모든 객체와 마찬가지로 System.Object에서 상속받습니다. 각 클래스 개체는 해당 데이터를 사용하여 해당 형식을 식별하는 32 비트 단어 (x86) 또는 64 비트 길이 단어 (x64)를 포함합니다. 그러나 값 유형 저장 위치는 그러한 클래스 객체 또는 참조를 보유하지 않으며 유형 객체의 단어가 저장되어 있지 않습니다. 대신, 각 프리미티브 값 유형 위치는 단순히 값을 나타내는 데 필요한 비트를 보유하며 각 struct 값 유형 저장 위치는 해당 유형의 모든 공개 및 비공개 필드의 내용을 보유합니다.

Double 변수를 Object 유형 중 하나로 복사하면 Double과 연결된 클래스 개체 유형의 새 인스턴스가 만들어지고 원본의 모든 바이트가 새 클래스 개체로 복사됩니다. 박스형 Double 클래스 유형은 Double 값 유형과 이름이 같지만 일반적으로 동일한 컨텍스트에서 사용할 수 없으므로 모호성을 유발하지 않습니다. 값 유형의 저장 위치는 저장된 유형 정보없이 원시 비트 또는 필드 조합을 보유합니다. 그러한 저장 위치를 ​​다른 저장 위치로 복사하면 모든 바이트가 복사되어 결과적으로 모든 공개 및 비공개 필드가 복사됩니다. 반대로 값 유형에서 파생 된 유형의 힙 오브젝트는 힙 오브젝트이며 힙 오브젝트처럼 작동합니다. C#은 값 유형 저장 위치의 내용을 마치 Object의 파생물 인 것처럼 간주하지만 저장 영역의 내용은 단순히 형식 시스템 외부의 바이트 모음입니다. 바이트가 나타내는 내용을 알고있는 코드에서만 액세스 할 수 있기 때문에 이러한 정보를 저장 위치 자체에 저장할 필요가 없습니다. 구조체에서 GetType을 호출 할 때 복싱의 필요성은 종종 섀도우가 아닌 비 가상 함수 인 GetType으로 설명되지만 실제 필요성은 값 유형 저장 위치의 내용 (위치 자체)에는 유형 정보가 없습니다.

관련 문제