传递给函数两次时,通用类型T的编译错误

Giu*_*olo 1 .net generics parameters casting interface

我可能遗漏了一些非常基本的东西,但我无法弄清楚为什么我得到某个代码的编译错误而且我没有用几乎相同的代码得到它.

所以我在这里得到一个错误:

//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
{
    ...
}
Run Code Online (Sandbox Code Playgroud)

但我不在这里:

//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
{
    ...
}
Run Code Online (Sandbox Code Playgroud)

基本上,如果我在传递的参数函数中使用泛型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);
    }
}
}
Run Code Online (Sandbox Code Playgroud)

非常感谢,

朱塞佩

Rom*_*ier 5

根据IBase接口,GetChildren方法总是返回IBase实例,而不是T实例.你有一个约束T,强制每个T实现IBase,但实现的一切IBase都不是类型T.

请注意,一个简单的解决方案应该是制作IBase泛型,并声明Foo如下:

class Foo : IBase<Foo> { /*...*/ }
Run Code Online (Sandbox Code Playgroud)

编辑:

这些Consume2方法工作得很好,因为T内部Consume2方法中的参数类型被推断为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
{

}
Run Code Online (Sandbox Code Playgroud)