2009-09-09 2 views
1

저는 매우 기본적인 것이 빠졌지 만 특정 코드에서 컴파일 오류가 발생하는 이유를 알 수 없으며 거의 ​​동일한 코드로 컴파일되지 않습니다. .함수에 두 번 전달할 때 일반 유형 T와 컴파일 오류가 발생했습니다.

그래서 내가 여기에 오류받을 수 있나요 :

//parent.GetChildren() returns a IEnumerable<IBase> 
F1<T>(T parent, Func<string, T, string> func) where T: IBase 
{ 
    F1(parent.GetChildren(), func); 
    //This would wok instead: 
    //F1(parent.GetChildren().Select(c=> (T)c), func); 
} 

F1<T>(IEnumerable<T> children, Func<string, T, string> func) where T: IBase 
{ 
    ... 
} 

을하지만 난하지 여기 수행

//parent.GetChildren() returns a IEnumerable<IBase> 
F1<T>(T parent, Func<string, string, string> func) where T: IBase 
{ 
    //Works, no casting required 
    F1(parent.GetChildren(), func); 
} 

F1<T>(IEnumerable<T> children, Func<string, string, string> func) where T: IBase 
{ 
    ... 
} 

을 내가 중 하나로서 전달 된 매개 변수 함수에서 일반 타입 T를 사용하여 기본적으로 경우의 매개 변수 다음과 같은 컴파일 오류가 발생합니다.

오류 1 : 'ConsoleApplication1.Program.FooConsumer.Consume1<ConsoleApplication1.Program.IBase>(System.Collections.Generic.IEnumerable<ConsoleApplication1.Program.IBase>, string, System.Func<string,ConsoleApplication1.Program.IBase,string>)'에 대한 오버로드 된 가장 일치하는 메서드에 잘못된 인수가 있습니다.

오류 2 : 인수 '3': 'System.Func<string,ConsoleApplication1.Program.IBase,string>'

다음

에 'System.Func<string,T,string>'에서 변환 할 수 없습니다 전체 예제 코드, 주석 코드를 참조하시기 바랍니다 (얻을 주석 컴파일 오류) :

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 

namespace ConsoleApplication1 
{ 
class Program 
{ 
    interface IBase 
    { 
     string GetName(); 
     IEnumerable<IBase> GetChildren(); 
    } 

    class Foo : IBase 
    { 
     private string _Name; 

     public Foo(string name) 
     { 
      _Name = name; 
     } 

     public string GetName() 
     { 
      return _Name; 
     } 

     public IEnumerable<IBase> GetChildren() 
     { 
      var r = new List<IBase>(); 
      r.Add(new Foo("foo 1")); 
      r.Add(new Foo("foo 2")); 
      return r; 
     } 
    } 


    class FooConsumer 
    { 
     public string Consume1<T>(IEnumerable<T> objects, string template, Func<string, T, string> func) where T : IBase 
     { 
      var s = ""; 
      foreach (var o in objects) 
      { 
       s += func(template, o); 
      } 
      return s; 
     } 
     public string Consume2<T>(IEnumerable<T> objects, string template, Func<string, string, string> func) where T : IBase 
     { 
      var s = ""; 
      foreach (var o in objects) 
      { 
       s += func(template, o.GetName()) + "\n"; 
      } 
      return s; 
     } 
     //Here if I don't cast each child as a T I get an error 
     public string Consume1<T>(T parent_object, string template, Func<string, T, string> func) where T : IBase 
     { 
      // return this.Consume1(parent_object.GetChildren(), template, func); //<-- UNCOMMENTING THIS WOULD NOT COMPILE 
      return this.Consume1(parent_object.GetChildren().Select(c => (T)c), template, func); 
     } 
     //Here I would expect it to behave identically, but instead I don't get an Error and code compiles fine. 
     //How can the last parameter be affecting the first parameter?! 
     public string Consume2<T>(T parent_object, string template, Func<string, string, string> func) where T : IBase 
     { 
      return this.Consume2(parent_object.GetChildren(), template, func); //<-- THIS CALL DOES NOT DO THE CAST BUT COMPILES JUST FINE!!! 
     } 

    } 

    static void Main(string[] args) 
    { 
     FooConsumer fc = new FooConsumer(); 
     Foo f = new Foo("parent"); 

     Func<string, IBase, string> func1 = (template, node) => 
      string.Format(template, node.GetName()); 

     Func<string, string, string> func2 = (template, name) => 
      string.Format(template, name); 


     string s1 = fc.Consume1(f, "<li>{0}</li>", func1); 

     string s2 = fc.Consume2(f, "<li>{0}</li>", func2); 

     Console.WriteLine("Executing first:"); 
     Console.WriteLine(s1); 
     Console.WriteLine("Executing second:"); 
     Console.WriteLine(s2); 
    } 
} 
} 

많은 감사,

주세페

답변

5

IBase 인터페이스에 따르면, GetChildren 방법은 항상을 반환인스턴스가 아니라 T 인스턴스입니다. T에 제약 조건이있어서 각 TIBase을 구현해야하지만 IBase을 구현하는 모든 것은 T 일 수 없습니다. 간단한 해결책은 IBase 제네릭 있도록해야하고, 그런 Foo를 선언

참고 :

편집
class Foo : IBase<Foo> { /*...*/ } 

:

Consume2 방법이 잘 작동하기 때문에 내부에서 T 매개 변수 유형 Consume2 방법은 이 아니라 IBase으로 추정됩니다.

public void Test() 
{ 
    Method1(new Foo("lol")); 
    // Same as 
    // Method1<Foo>(new Foo("lol")); 
} 

public void Method1<T>(T parent) where T : IBase 
{ 
    Method1(parent.GetChildren()); 
    // Same as : 
    // Method1<IBase>(parent.GetChildren()); 
    // since GetChildren() returns IEnumerable<IBase>, not IEnumerable<Foo> 
} 

public void Method1<T>(IEnumerable<T> children) where T : IBase 
{ 

} 
+0

+1 훌륭한 설명. –

+0

감사합니다.하지만 Consume2가 캐스팅없이 작동하는 이유는 무엇입니까? –

2

이 전화를 추론 할 수없는 돌출되고, 그냥 도움을

//Here if I don't cast each child as a T I get an error 
public string Consume1<T>(T parent_object, string template, Func<string, T, string> func) where T : IBase 
{ 
    return this.Consume1((IEnumerable<T>)parent_object.GetChildren(), template, func); //<-- UNCOMMENTING THIS WOULD NOT COMPILE 
} 

을 필요로이 지금

0

로맹이 게시물에 대한 감사 컴파일합니다. 그것은 설명하고 아마도 그 질문에 답할 것입니다.하지만 저를 위해 그리고 독자를위한 집행으로 좀 더 명확하게 반복하고 싶습니다. 컴파일러는 함수 호출이 T2의 유형을 추론하는 F1<T1>(parent.GetChildren(), func);을 분석

//parent.GetChildren() returns a IEnumerable<IBase> 
F1<T1>(T1 parent, Func<string, T1, string> func) where T1: IBase 
{ 

    //This does not work 
    F1<T1>(parent.GetChildren(), func); 
    //This would work instead: 
    //F1<T1>((IEnumerable<T1>)parent.GetChildren()), func); 
} 
//Note: I changed the generic type names to T1 and T2 since they are 
//two different Types as far as the compiler is concerned. 
//Using for both T may yield to the false assumption that they must be 
//of the same type. 
F1<T2>(IEnumerable<T2> children, Func<string, T2, string> func) where T2: IBase 
{ 
    /* implementation */ 
} 

:

은 기본적으로 여기에 앞에서 제시 한 예이다.

T2는 parent.GetChildren()IEnumerable<IBase>을 명시 적으로 반환하므로 IBase 유형이어야합니다. 대신 세 번째 매개 변수는 호출 함수 세 번째 매개 변수 (Func<string, T1, string> func)에서 오는 함수입니다. T1에 부과 된이 매개 변수의 유일한 제약은 IBase를 구현하는 것입니다. 따라서 컴파일러가 아는 한 T1은 모든 유형이 될 수 있지만이 단계에서는 IBase 유형이어야하고 더 이상 첫 번째 함수에 전달되는 매개 변수에서 유추되지 않습니다.

따라서 명시 적 캐스트가 필요합니다.

추가 : F1<T1>(parent.GetChildren(), func);F1<T1> 또한 첫 번째 매개 변수의 유형과 충돌합니다.

실제로 본인의 실제 스레드에 게시 된 전체 코드 예제에서는 Consume2이 항상 내부 호출 함수의 형식을 IBase로 유추하기 때문에 작동합니다. 주석 처리 된 코드보기 :

public string Consume2<T>(T parent_object, string template, Func<string, string, string> func) where T : IBase 
{ 
    //return this.Consume2<T>(parent_object.GetChildren(), template, func); // Errors: T conflicts with the first parameter generic type  
    //return this.Consume2<IBase>(parent_object.GetChildren(), template, func); // Works: Explicitly setting the type 
    return this.Consume2(parent_object.GetChildren(), template, func); // Works: The type is inferred from the first parameter only 
} 
관련 문제