locked
Method Overload resolution query RRS feed

  • Question

  • Hi Team,

       I have the query below on overload resolution. I' am using VS2008 Dev edition and .net Framework 3.5. Please see the code below

    class TestTypeInterface

        {
            static void Main()
            {
                Foo("Hello");
            }

            //static void Foo(string[] x)
            //{
            //    Console.WriteLine("string[]");
            //}

            static void Foo(object x)
            {
                Console.WriteLine("object");
            }

            static void Foo<T>(params T[] x)
            {
                Console.WriteLine("params T[]");
            }
        }

    In the code above the control is reaching the params version of Foo probably because string type is more direct/specific than the object type. If I comment out the params version and uncomment the "string[]" version as shown below

    class TestTypeInterface
        {
            static void Main()
            {
                Foo("Hello");
            }

            static void Foo(string[] x)
            {
                Console.WriteLine("string[]");
            }

            static void Foo(object x)
            {
                Console.WriteLine("object");
            }

            //static void Foo<T>(params T[] x)
            //{
            //    Console.WriteLine("params T[]");
            //}
        }

    why is control going to the "object" version of Foo rather than the string[] version, especially, when in the first snippet of code, the parameter "params [] x" will get translated to "string[] x"?

    which specific section in Method overload resolution specification for CSharp is in-the-play here?

    Any clues would be helpful 

    Thanks and Regards,

    Ganesh

     


    Sunday, October 18, 2015 3:03 AM

Answers

  • "I checked the MSIL with ILDASM and there is an array in the formal parameter, see below"

    The problem is that what happens in MSIL is irrelevant as far as overload resolution is concerned. If the language specification says that params arguments are treated as individual arguments instead of a single array then that's how overload resolution is done even if in reality an array will be created and used as argument.

    To quote the relevant text from the C# spec:

    7.5.3.1 "For a function member that includes a parameter array, if the function member is applicable by the above rules, it is said to be applicable in its normal form. If a function member that includes a parameter array is not applicable in its normal form, the function member may instead be applicable in its expanded form: 

    The expanded form is constructed by replacing the parameter array in the function member declaration with zero or more value parameters of the element type of the parameter array such that the number of arguments in the argument list A matches the total number of parameters. If A has fewer arguments than the number of fixed parameters in the function member declaration, the expanded form of the function member cannot be constructed and is thus not applicable"

    Since there's no implicit conversion between string and string[] the normal form cannot be used and the expanded form needs to be used. The expanded form is built as described above and this form is used only for overload resolution, the generated code can and will be different.

    • Marked as answer by Ganeshkumarps Tuesday, October 20, 2015 4:34 AM
    Monday, October 19, 2015 12:50 PM

All replies

  • "why is control going to the "object" version of Foo rather than the string[] version"

    How could the string[] version could possibly be called given that the argument is of type string and not string[]?

    "especially, when in the first snippet of code, the parameter "params [] x" will get translated to "string[] x"?"

    It gets translated to string[] only because params is present. If you remove params then the generic overload won't be used just like string[] overload isn't used.

    Note that for purpose of overload resolution the compiler treats the params overload as a method with a single parameter of type T (in this particular case). From this it's easy to see that the in the first case the params overload is picked because the conversion from string to string is better than the conversion from string to object.

    See 7.5.3.1 (especially the paragraphs about the normal and expanded forms) and 7.5.3.2 in the C# specification.

    Sunday, October 18, 2015 9:26 AM
  • Hi Ganeshkumarps,

    [why is control going to the "object" version of Foo rather than the string[] version]

    Because compiler can't convert string to string[]. If you add below method. I think Foo("Hello") will reach it.

    static void Foo(params string[] x)
    {
       Console.WriteLine("string[]");
    }
    
    Best Regards,
    Li Wang

    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
    Click HERE to participate the survey.

    Monday, October 19, 2015 6:49 AM
  • Hi Mike,

      Thanks for your response to my query, but I' am still not clear on your comment below

    "Note that for purpose of overload resolution the compiler treats the params overload as a method with a single parameter of type T (in this particular case)."

     the single parameter in the params version is actually an array of type T isn't it rather than just T. I checked the MSIL with ILDASM and there is an array in the formal parameter, see below

     IL_0011:  call       void BaseDeriveCalls.TestTypeInterface::Foo<string>(!!0[])

    In the debugger also we can see it is treated as string []

    if the generic version eventually gets translated to string[] then shouldn't the object version be a better candidate for overload resolution?

    I Also removed the params and tried then it resolved to the object version as you mentioned and is understandable. I then removed the object version and with out params it gave the error below, which is understandable

    Error:- The type arguments for method 'BaseDeriveCalls.TestTypeInterface.Foo<T>(T[])' cannot be inferred from the usage. Try specifying the type arguments explicitly.

        class TestTypeInterface
        {
            static void Main()
            {
                Foo("hello");
            }
    // no params
            static void Foo<T>(T[] x)
            {
                Console.WriteLine("T[]");
            }
        }

    So I modified the call like 

     Foo(new string[] {"hello"})

    which compiled correctly and the MSIL looked like

      IL_0011:  call       void BaseDeriveCalls.TestTypeInterface::Foo<string>(!!0[])

    So with params version it is generating the same MSIL as that for an array call (even when in the call there is no array)

    why is it that, with params keyword the call Foo("hello") is getting resolved to an array version of generic type and the seemingly more closer(non-array) object version ignored?

    Regards,

    Ganesh

    Monday, October 19, 2015 9:19 AM
  • "string" is as distinct from "string[]", as "int" is from "SqlConnection"

    If it is a params parameter, any single instance of the type will be converted into a array of the type at compile time. Even if that array has jsut once instance.
    Keep in mind that there are strict limits inlcuding "only one params per function" and "params must come last", to allow the compiler to pick the right version.
    All type checks and all overload indentification is done at compile time, not at runtime!
    All generic types are checked at compiled time.
    Also "params" is a part of the C# langauge definition, NOT the .NET Framework.

    Please use code blocks for your code. Posting it raw makes it very hard to read.
    If you had just the params version (wich makes a lot of sense here actually), the compiler will just reinpret this:

        class TestTypeInterface
        {
            static void Main()
            {
                Foo("Hello");
            }
    
            static void Foo<T>(params T[] x)
            {
                Console.WriteLine("params T[]");
            }
        }

    too this:

        class TestTypeInterface
        {
            static void Main()
            {
                Foo(new string[] {"Hello"});
            }
    
            static void Foo<T>(params T[] x)
            {
                Console.WriteLine("params T[]");
            }
        }

    at compile time.

    If the compiler has the choice between a "params T", "string" and "object" variant, it will choose:

    string variant in case of a single string
    params T variant in case of any other type, single or array (except for single objects)
    The object version for single objects. maybe*.
    A generic, "fill the type in yourself compiler" version of a function is always specific for any type. That is what it is there for.

    *Not even sure it would use it then. And in any case any list of objects would be handeled by the generic version again. T can also be object after all.

    Monday, October 19, 2015 11:02 AM
  • "I checked the MSIL with ILDASM and there is an array in the formal parameter, see below"

    The problem is that what happens in MSIL is irrelevant as far as overload resolution is concerned. If the language specification says that params arguments are treated as individual arguments instead of a single array then that's how overload resolution is done even if in reality an array will be created and used as argument.

    To quote the relevant text from the C# spec:

    7.5.3.1 "For a function member that includes a parameter array, if the function member is applicable by the above rules, it is said to be applicable in its normal form. If a function member that includes a parameter array is not applicable in its normal form, the function member may instead be applicable in its expanded form: 

    The expanded form is constructed by replacing the parameter array in the function member declaration with zero or more value parameters of the element type of the parameter array such that the number of arguments in the argument list A matches the total number of parameters. If A has fewer arguments than the number of fixed parameters in the function member declaration, the expanded form of the function member cannot be constructed and is thus not applicable"

    Since there's no implicit conversion between string and string[] the normal form cannot be used and the expanded form needs to be used. The expanded form is built as described above and this form is used only for overload resolution, the generated code can and will be different.

    • Marked as answer by Ganeshkumarps Tuesday, October 20, 2015 4:34 AM
    Monday, October 19, 2015 12:50 PM
  • Mike, Thanks for quoting the spec and clarifying... though I still need to get my head around that spec... I have marked it as answer as i' am contend with your clarification.. 

    Thanks again

    Regards,

    Ganesh

    Tuesday, October 20, 2015 4:36 AM
  • Hi Christopher,

     thanks for your response to my query. I shall note to use code blocks in my future queries. I have marked Mike's reply as answer as it contends me more though your reply seems to be on the same lines

    Regards,

    Ganesh

    Thursday, October 22, 2015 2:50 AM