2009-09-09 99 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,T,string>」轉換爲「System.Func<string,ConsoleApplication1.Program.IBase,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方法總是返回IBase實例,而不是T實例。您對T有約束,這會強制每個T實施IBase,但實施IBase的所有內容都不能是T類型。

需要注意的是一個簡單的解決方案應該是讓IBase通用的,並申報Foo這樣的:

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

編輯:

Consume2方法工作得很好,因爲在內部的T參數類型推測方法是IBase,而不是Foo

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很好的解釋。 – 2009-09-09 14:16:18

+0

謝謝,我明白,但爲什麼消費2沒有任何鑄造工作? – 2009-09-09 14:32:26

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

羅曼感謝您的帖子。它確實解釋了並且可能回答了這個問題,但是我想更明確地將它作爲我和讀者的一個行爲來進行迭代。

基本上這裏是我先前提出的實施例:

//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 */ 
} 

當編譯器分析該函數調用F1<T1>(parent.GetChildren(), func);它具有推斷T2的類型。

T2必須是IBase類型,因爲parent.GetChildren()明確返回IEnumerable<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 
}