2012-05-11 2 views
16

이 질문은 시간이 오래 걸리므로 제발 같이 따라주십시오.Func <> s의 List <>는 일반 반환 형식의 오류를 컴파일하지만 그 이유는 무엇입니까?

은 내가 문자열의 세트와 각각의 문자열에 대한 해당 제네릭 메서드 호출 사이의 매핑을 만들어야합니다. 그러나 나는 컴파일 문제에 직면 해있다.

제 생각에는 Dictionary<>을 사용하고 있지만 문제는 List<>에 대해서도 동일하게 존재합니다. 단순화를 위해 아래 예에서 List<>을 사용하고 있습니다.

public abstract class MyBase { /* body omitted */ } 
public class MyDerived1 : MyBase { /* body omitted */ } 
public class MyDerived2 : MyBase { /* body omitted */ } 

그리고 다른 클래스의 방법 :

이 세 클래스 고려

public class SomeClass 
{ 
    public void SomeFunc() 
    { 
     var test = new Test(); 

     var list1 = new List<Func<MyBase>> 
      { 
       test.GetT<MyDerived1>, 
       test.GetT<MyDerived2> 
      }; 
    } 
} 

: 다른 클래스에서

public class Test 
{ 
    public T GetT<T>() where T : MyBase { /* body omitted */ } 
} 

를,이 같은 List<Func<MyBase>>을 선언 할 수 있습니다 이것은 모두 괜찮아요.

하지만, 내가이 같은 제네릭 클래스를 반환하는 함수 갖고 싶어 :

public class RetVal<T> where T : MyBase { /* body omitted */ } 

public class Test 
{ 
    public RetVal<T> GetRetValT<T>() where T : MyBase 
    { 
     return null; 
    } 
} 

을 그리고이 기능을 사용하여 해당 List<>을 만들려고합니다. 즉 목록 >>?

public class Class1 
{ 
    public void SomeFunc() 
    { 
     var test = new Test(); 

     var list2 = new List<Func<RetVal<MyBase>>> 
      { 
       test.GetRetValT<MyDerived1>, // compile error 
       test.GetRetValT<MyDerived2> // compile error 
      }; 
    } 
} 

컴파일 오류가 Expected a method with 'RetVal<MyBase> GetRetValT()' signature이됩니다.

그래서이 문제를 해결할 수있는 방법이 있습니까? 아니면 내 문자열을 만드는 데 사용할 수있는 대체 방법이 있습니까? 매핑을 호출 하시겠습니까?

+1

그것은 분산 문제입니다. 기본적으로,'RetVal '*는'RetVal '이 아닙니다. 시간이있을 때 자세한 내용. –

+0

@ 존스 쉘 (JonSkeet)이 말한 것은 사실입니다. 그것은 C#의 법칙입니다. 'RetVal '의 MyDerived를'RetVal '의 MyBase에 넣고 싶다면'RetVal'을 타입이없는 generic도 받아 들여야합니다. 즉,'RetVal '입니다. 여기 예제를 참조하십시오 : http://www.ienablemuch.com/2012/05/generics-object-orientation-untyped.html –

+0

나는 이것에 대해 전적으로 근본적이지는 않지만 제네릭을 돌려 주려는 아이디어는 아닙니다. generic이 (결국) 컴파일러가 컴파일 타임에 알고있는 것에 해결해야한다는 개념을 위반합니까? 즉, 컴파일러가 매우 오류를 throw하는 이유는 특정 유형을 반환 할 수있는 메소드를 제공하기를 기대하기 때문입니다. 내가 말했던 바로 그 관찰은 여기 근거지가되는 길일 수도 있습니다 ... –

답변

16

C#은 인터페이스에 대한 공분산 만 허용합니다. 즉, RetVal<MyDerived1>RetVal<MyBase>으로 자동 전송할 수 없습니다.클래스

var list2 = new List<Func<IRetVal<MyBase>>> 
     { 
      test.GetRetValT<MyDerived1>, 
      test.GetRetValT<MyDerived2> 
     }; 
+0

다른 AppDomain ***에서'dataSources = new List >>'을 사용하는 경우 문제가 발생했습니다. – Kiquenet

+0

@Kiquenet : 나는 당신의 의견을 이해하지 못합니다. 당신은 명확히 할 수 있습니까? – StriplingWarrior

2

최근에 비슷한 문제가있었습니다.

C#을 인터페이스 구현이나 가상 메소드 overrding의 목적 반환형 공분산을 지원하지 않는다.

Does C# support return type covariance? : 자세한 내용은이 질문을 참조하십시오.

public RetVal<R> GetRetValT<T,R>() where T : MyBase where R : MyBase 
{ 
    return null; 
} 

//Then, change this to: 
test.GetRetValT<MyDerived1, MyBase> 
7

문제는 제네릭의 고전적인 공분산/contravariance은 다음과 같습니다

내 행함이 해킹 할 수 있습니다. 당신은 MyDerived1 때문에 MyDerived2RetVal<MyDerived1> 상속 RetVal<MyBase>에서, 그리고 그것을하지 않는, MyBase에서 상속한다고 가정한다.

public interface IRetVal<out T> { ... } 

public class RetVal<T> : IRetVal<T> { ... } 
: 가능하면 공변 될 JS 댓글에서 지적 하듯, 더 나은 아직

var list2 = new List<Func<RetVal<MyBase>>> 
     { 
      () => (MyBase)test.GetRetValT<MyDerived1>, 
      () => (MyBase)test.GetRetValT<MyDerived2> 
     }; 

하거나 RetVal<T>을 변경

이 문제를 해결하는 가장 쉬운 방법으로 코드를 변경 아마

+3

C# 4에서'Func '*는 T에서 공변 (covariant)하기 때문에 첫 번째 예제가 작동합니다. 여기에 'RetVal '이 있는데 여기에는 공변 적이 지 않습니다. –

+1

오른쪽, 죄송합니다 ... 똑바로됩니다 –

3

일반 유형 매개 변수가 공변 될 수는 없지만, 그들은을 위해 공변 될 수

public interface IRetVal<out T> 
{ 

} 
public class RetVal<T> : IRetVal<T> where T : MyBase { /* body omitted */ } 

public class Test 
{ 
    public IRetVal<T> GetRetValT<T>() where T : MyBase 
    { 
     return null; 
    } 
} 

그런 다음이 코드가 작동합니다 RetVal이 공변해야하는 경우,과 같이, 그것을위한 인터페이스를 만들 인터페이스. 형식 매개 변수가 공변수로 선언 된 경우 RetVal<T>, 대신 IRetVal<T> 인터페이스로 원하는 작업을 수행 할 수 있습니다. 인터페이스 유형 매개 변수를 공 변 (covariant)으로 선언하려면 "출력"위치에서만 사용해야합니다. 설명하기

이 코드는 컴파일되지 않습니다 :

interface IRetVal<out T> 
{ 
    T Value { get; } 
    void AcceptValue(T value); 
} 

코드 컴파일 얻으려면, 당신은 형식 매개 변수에서 out 수정을 제거하거나가 T를 사용하기 때문에합니다 (AcceptValue 방법을 제거해야 하나 매개 변수 : 입력 위치). IRetVal<out T> 인터페이스와

, 당신은이 작업을 수행 할 수 있습니다

public class MyBase { } 
public class MyDerived1 : MyBase { } 
public class MyDerived2 : MyBase { } 

public interface IRetVal<out T> where T : MyBase { /* body omitted */ } 

public class Test 
{ 
    public IRetVal<T> GetRetValT<T>() where T : MyBase 
    { 
     return null; 
    } 
} 

public class Class1 
{ 
    public void SomeFunc() 
    { 
     var test = new Test(); 

     var list = new List<Func<IRetVal<MyBase>>> 
     { 
      test.GetRetValT<MyDerived1>, 
      test.GetRetValT<MyDerived2> 
     }; 
    } 
}