簡體   English   中英

兩次傳遞給函數時,通用類型T的編譯錯誤

[英]Compilation Error with Generic Type T when passed to a function twice

我可能缺少一些非常基本的東西,但是我無法弄清楚為什么我在使用某些代碼時會遇到編譯錯誤,而在幾乎完全相同的代碼中卻找不到它。

所以我確實在這里得到一個錯誤:

//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
{
    ...
}

基本上,如果我在傳遞的參數函數中使用通用Type 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);
    }
}
}

非常感謝,

朱塞佩

根據IBase接口, GetChildren方法始終返回IBase實例,而不是T實例。 您對T有一個約束,它迫使每個T都實現IBase ,但是實現IBase都不能屬於T類型。

注意,一個簡單的解決方案應該是使IBase通用,並這樣聲明Foo

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

編輯:

Consume2方法工作得很好,因為內部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
{

}

突出無法推斷出此通話,只需要一些幫助

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

現在可以編譯

羅曼(Romain)感謝您的帖子。 它確實可以解釋並可能回答該問題,但是我想更明確地遍歷該問題,作為對我和讀者的一種執行。

基本上,這是我之前提出的示例:

//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 )的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 
 }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM