【问题标题】:Compilation Error with Generic Type T when passed to a function twice将泛型类型 T 传递给函数两次时出现编译错误
【发布时间】:2009-09-09 14:02:00
【问题描述】:

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

所以我确实在这里遇到了错误:

//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&lt;ConsoleApplication1.Program.IBase&gt;(System.Collections.Generic.IEnumerable&lt;ConsoleApplication1.Program.IBase&gt;, string, System.Func&lt;string,ConsoleApplication1.Program.IBase,string&gt;)”的最佳重载方法匹配有一些无效参数

错误 2:参数“3”:无法从“System.Func&lt;string,T,string&gt;”转换为“System.Func&lt;string,ConsoleApplication1.Program.IBase,string&gt;”

这里是完整的示例代码,请参考注释代码(取消注释会得到编译错误):

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

非常感谢,

朱塞佩

【问题讨论】:

    标签: .net generics parameters casting interface


    【解决方案1】:

    根据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
    {
    
    }
    

    【讨论】:

    • 谢谢,我明白了,但是为什么 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
    }
    

    现在编译

    【讨论】:

      【解决方案3】:

      罗曼感谢您的帖子。它确实解释并可能回答了这个问题,但我想更明确地迭代它,作为对我和读者的执行。

      基本上这里是我之前提出的例子:

      //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&lt;T1&gt;(parent.GetChildren(), func); 时,它必须推断 T2 的类型。

      T2 必须是 IBase 类型,因为 parent.GetChildren() 显式返回 IEnumerable&lt;IBase&gt;。第三个参数是来自调用函数第三个参数 (Func&lt;string, T1, string&gt; func)) 的函数。此参数对 T1 的唯一约束是要实现 IBase。因此,就编译器所知,T1 可以是任何类型,而在这个阶段它要求它是 IBase 类型,并且不再从传递给第一个函数的任何参数中推断出来。

      因此需要显式转换!

      补充: 同样在F1&lt;T1&gt;(parent.GetChildren(), func);F1&lt;T1&gt;会和第一个参数的类型冲突。

      事实上,在我在初始线程中发布的完整代码示例中,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 
       }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-05-09
        • 1970-01-01
        • 2022-08-19
        • 1970-01-01
        • 2011-02-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多