C #에서 제네릭을 사용한 공분산 반공 분산 이해 문제


115

다음 C # 코드가 컴파일되지 않는 이유를 이해할 수 없습니다.

보시다시피 IEnumerable<T>매개 변수가있는 (그리고 인터페이스로 T제한됨) 정적 제네릭 메서드 Something이 있으며이 IA매개 변수는 암시 적으로 IEnumerable<IA>.

설명은 무엇입니까? (나는 그것이 작동하지 않는 이유를 이해하기 위해 해결 방법을 검색하지 않습니다).

public interface IA { }
public interface IB : IA { }
public class CIA : IA { }
public class CIAD : CIA { }
public class CIB : IB { }
public class CIBD : CIB { }

public static class Test
{
    public static IList<T> Something<T>(IEnumerable<T> foo) where T : IA
    {
        var bar = foo.ToList();

        // All those calls are legal
        Something2(new List<IA>());
        Something2(new List<IB>());
        Something2(new List<CIA>());
        Something2(new List<CIAD>());
        Something2(new List<CIB>());
        Something2(new List<CIBD>());
        Something2(bar.Cast<IA>());

        // This call is illegal
        Something2(bar);

        return bar;
    }

    private static void Something2(IEnumerable<IA> foo)
    {
    }
}

Something2(bar)줄에있는 오류 :

인수 1 : 'System.Collections.Generic.List'에서 'System.Collections.Generic.IEnumerable'로 변환 할 수 없습니다.



12
T참조 유형으로 제한되지 않았습니다 . 조건 where T: class, IA을 사용하면 작동합니다. 연결된 답변에 자세한 내용이 있습니다.
Dirk '

2
@Dirk 나는 이것이 중복으로 표시되어야한다고 생각하지 않습니다. 여기에서 개념 문제가 값 유형에 대한 공분산 / 반공 분산 문제라는 것은 사실이지만, 여기서 구체적인 경우는 "이 오류 메시지가 의미하는 바"일뿐만 아니라 "클래스"를 포함하는 것만으로도 자신의 문제를 해결하지 못하는 경우입니다. 나는 미래의 사용자가이 오류 메시지를 검색하고이 게시물을 찾고 만족할 것이라고 믿습니다. (자주 그렇듯이)
Reginald Blue

Something2(foo);직접 말하여 상황을 재현 할 수도 있습니다. ( 은 제네릭 메서드에 의해 선언 된 유형 매개 변수) .ToList()를 얻기 위해 돌아 다니는 것은 이것을 이해하는 데 필요하지 않습니다 (a 는 ). List<T>TList<T>IEnumerable<T>
Jeppe Stig Nielsen

@ReginaldBlue 100 %, 같은 것을 게시하려고했습니다. 유사한 답변은 중복 질문이 아닙니다.
UuDdLrLrSs

답변:


218

오류 메시지는 정보가 충분하지 않으며 그것은 내 잘못입니다. 미안합니다.

겪고있는 문제는 공분산이 참조 유형에서만 작동한다는 사실의 결과입니다.

지금 "하지만 IA참조 유형입니다" 라고 말하고있을 것입니다 . 네, 그렇습니다. 하지만 당신은 그 말을하지 않았다 T 같다 IA . 당신은 그것이 구현T 하는 유형 이고 값 유형은 인터페이스구현할 수 있다고 말했습니다 . 따라서 공분산이 작동할지 여부를 알 수 없으며이를 허용하지 않습니다.IA

공분산이 작동하도록하려면 컴파일러에게 유형 매개 변수가 인터페이스 제한 조건 class뿐만 아니라 제한 조건 이있는 참조 유형임을 알려야합니다 IA.

오류 메시지는 근본적인 문제이기 때문에 공분산은 참조 유형의 보장을 필요로하기 때문에 변환이 불가능하다는 사실을 말해야합니다.


3
왜 당신 잘못이라고 했죠?
user4951

77
@ user4951 : 오류 메시지를 포함하여 모든 변환 검사 논리를 구현했기 때문입니다.
Eric Lippert

@BurnsBA 이것은 인과 적 의미에서 "결함"일뿐입니다. 기술적으로 구현과 오류 메시지가 완벽하게 정확합니다. (비 변환성에 대한 오류 설명이 실제 이유를 자세히 설명 할 수 있다는 것뿐입니다.하지만 제네릭으로 좋은 오류를 생성하는 것은 어렵습니다. 몇 년 전의 C ++ 템플릿 오류 메시지에 비해 이것은 명료하고 간결합니다.)
Peter-Reinstate Monica

3
@PeterA. Schneider : 감사합니다. 그러나 Roslyn에서 오류보고 논리를 설계하는 주된 목표 중 하나는 특히 위반 된 규칙뿐 아니라 가능한 경우 "근본 원인"을 식별하는 것이 었습니다. 예를 들어 오류 메시지의 내용은 무엇 customers.Select(c=>c.FristName)입니까? C # 사양은 이것이 오버로드 해결 오류라는 것을 매우 분명하게 보여 줍니다. 해당 람다를 사용할 수있는 Select라는 적용 가능한 메서드 집합이 비어 있습니다. 그러나 근본 원인은 FirstName오타 가 있다는 것 입니다.
Eric Lippert

3
@ PeterA.Schneider : 제네릭 유형 추론 및 람다와 관련된 시나리오에서 적절한 휴리스틱을 사용하여 개발자에게 가장 도움이 될 오류 메시지를 추론하도록 많은 작업을 수행했습니다. 그러나 특히 분산이 관련된 경우 변환 오류 메시지에 대해 훨씬 덜 좋은 작업을 수행했습니다. 나는 항상 그것을 후회했습니다.
Eric Lippert 2018

26

일반적인 제약 조건에 익숙하지 않은 사람들을위한 코드 예제로 Eric의 훌륭한 내부자 답변을 보완하고 싶었습니다.

다음 Something과 같이 서명을 변경하십시오 . class제약 조건 이 먼저 와야 합니다.

public static IList<T> Something<T>(IEnumerable<T> foo) where T : class, IA

2
궁금한데 ... 주문이 중요한 이유가 정확히 무엇인가요?
Tom Wright

5
@TomWright-사양에는 물론 많은 "왜?"에 대한 답변이 포함되어 있지 않습니다. 질문은, 그러나에 이 경우 는 제약의 세 가지 유형이 있다는 것을 분명히 않으며, 세 가지가 사용될 때 특별히해야primary_constraint ',' secondary_constraints ',' constructor_constraint
Damien_The_Unbeliever

2
@TomWright : Damien이 맞습니다. 파서 작성자의 편리함 외에는 내가 아는 특별한 이유가 없습니다. 내 druthers가 있다면 유형 제약에 대한 구문이 훨씬 장황 해질 것 입니다. class이것은 "클래스"가 아니라 "참조 유형"을 의미하기 때문에 나쁘다. 나는 자세한 같은 더 행복했을 것이다where T is not struct
에릭 Lippert의
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.