简体   繁体   中英

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

I'm probably missing something very basic but I cannot figure out why I get a compilation error with a certain code and I don't get it in an almost identical code.

So I do get an error here:

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

but I don't here:

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

Basically if I use the generic Type T in the passed parameter function as one of its parameters I get the following compilation errors:

Error 1: The best overloaded method match for ' ConsoleApplication1.Program.FooConsumer.Consume1<ConsoleApplication1.Program.IBase>(System.Collections.Generic.IEnumerable<ConsoleApplication1.Program.IBase>, string, System.Func<string,ConsoleApplication1.Program.IBase,string>) ' has some invalid arguments

Error 2: Argument '3': cannot convert from ' System.Func<string,T,string> ' to ' System.Func<string,ConsoleApplication1.Program.IBase,string> '

Here is the full example code, please refer to commented code (uncomment to get compilation error):

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

Many Thanks,

Giuseppe

According to IBase interface, GetChildren method always returns IBase instances, not T instances. You have a constraint on T , which forces each T to implement IBase , but everything that implements IBase can't be of type T .

Note that a simple solution should be to make IBase generic, and to declare Foo like that:

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

EDIT :

The Consume2 methods works just fine because the T parameter type in the inner Consume2 method is inferred as being IBase , not 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
{

}

is jut not able to infer this call, it just needs some help

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

this now compiles

Romain thank you for your post. It does explain and probably answers the question but I'd like to iterate through it a little more explicitly as an execise for me and for the reader.

Basically here is the example I proposed earlier:

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

when the compiler analyses the function call F1<T1>(parent.GetChildren(), func); it has to infer the type of T2.

T2 must be of type IBase since parent.GetChildren() explicitly returns a IEnumerable<IBase> . The 3rd parameter instead is a function that is coming from the calling function third parameter ( Func<string, T1, string> func )). This parameter's only constraint imposed on T1 is to be implementing IBase. Therefore, as far as the compiler knows, T1 can be of any type while it at this stage it required it to be of type IBase and no longer inferred from whatever parameters are passed to the first function.

Therefore an explicit cast is required!

ADDITION: Also in F1<T1>(parent.GetChildren(), func); F1<T1> would conflict with the type of the first parameter.

In fact, in the full code example that I posted in my inital thread, Consume2 works only because it always infers the type of the inner called function to be IBase. See commented code:

 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 
 }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM