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

时间:2009-09-09 14:02:01

标签: .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
{
    ...
}

但我不在这里:

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

非常感谢,

朱塞佩

3 个答案:

答案 0 :(得分:5)

根据IBase接口,GetChildren方法始终返回IBase个实例,而不是T个实例。您对T有约束,强制每个T实施IBase,但实施IBase的所有内容都不能是T类型。

请注意,一个简单的解决方案应该是IBase通用,并像这样声明Foo

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

编辑:

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
{

}

答案 1 :(得分: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
}

现在编译

答案 2 :(得分: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 
 }