1

I was testing a simple program to overload integer parameters:

class Program
{
    public static void Foo(Int16 value)
    {
        Console.WriteLine("Int16");
    }

    public static void Foo(Int32 value)
    {
        Console.WriteLine("Int32");
    }

    public static void Foo(Int64 value)
    {
        Console.WriteLine("Int64");
    }

    static void Main(string[] args)
    {
        Foo(10);            
    }
}

Now I know that the capacity of these types is this:

Type      Capacity

Int16 -- (-32,768 to +32,767)

Int32 -- (-2,147,483,648 to +2,147,483,647)

Int64 -- (-9,223,372,036,854,775,808 to +9,223,372,036,854,775,807)

Now Foo(10) calls the Int32 overload. Why? Can't the value of 10 fit in an Int16?

What confuses me more, is that when I remove the Int32 overload, the Int16 overload is called. Why is that?

CodeCaster
  • 147,647
  • 23
  • 218
  • 272
Sabyasachi Mishra
  • 1,677
  • 2
  • 31
  • 49
  • 17
    _"If the literal has no suffix, it has the first of these types in which its value can be represented: int, uint, long, ulong."_ https://msdn.microsoft.com/en-us/library/aa664674%28v=vs.71%29.aspx?f=255&MSPPError=-2147217396 I can't find a duplicate that quick, but I'm sure this has been answered before. – CodeCaster Mar 03 '17 at 06:54
  • It's interesting though that the Int16 overload is chosen if you remove the Int32 overload. Perhaps edit your question to focus on that. – CodeCaster Mar 03 '17 at 07:06
  • 2
    http://stackoverflow.com/a/16459680/17034 – Hans Passant Mar 03 '17 at 07:08
  • I have simplified your question so it is more readable, more relevant and more to the point. Do you agree? – CodeCaster Mar 03 '17 at 08:36
  • @CodeCaster reading your quote, you would expect long=Int64 to be called, instead of Int16 when the Int32 overload is removed... – JHBonarius Mar 03 '17 at 08:44
  • 2
    @J.H. yes, that's what everyone would expect. Read Hans' link and Eric Lippert's comment to that. – CodeCaster Mar 03 '17 at 08:45
  • 1
    @J.H.Bonarius Not so. The literal Int32 *can* hold the value, so the type of the literal is Int32. But it is a constant value, so the compiler can use the Int16 overload, even though it couldn't with a variable. – Luaan Mar 03 '17 at 08:46
  • Why not post it here (with some modifications)? – JHBonarius Mar 03 '17 at 08:48
  • See marked duplicate for discussion on overloading when dealing with integral types and literals. Short version: your literal has type `int`, so as long as an overload matches, that will be picked. If you don't have an `int` overload, then the question of "applicability" comes in, where the implicit conversion from your `int` literal to a `short` value allows the `Int16` overload to be used (`short` is preferred, because it is "more specific" than `long`) – Peter Duniho Mar 03 '17 at 09:11
  • See also http://stackoverflow.com/questions/31662284/c-sharp-parameter-implicit-conversion, http://stackoverflow.com/questions/19011863/overloaded-method-selection-logic, and http://stackoverflow.com/questions/9017363/ambiguous-method-overloading – Peter Duniho Mar 03 '17 at 09:12
  • @Peter my duplicate finding skills are lacking these days... Thanks. – CodeCaster Mar 03 '17 at 09:33
  • 1
    @CodeCaster Ach ja, Carvanal is net geweest. Oh nee, je bent van boven de rivieren. – JHBonarius Mar 03 '17 at 09:45
  • 1
    Summing up: the *type* of expression `10` is `int`. It is *implicitly convertible* to all the built-in numeric types. Therefore when asked to make a choice of the best overload, overload resolution will first choose the *exact* match -- `int` -- if available. If not, then it will choose the *unique most specific type* available, if there is one. `short` is more specific than `long` because all shorts are convertible to long but not all longs are convertible to short. – Eric Lippert Mar 04 '17 at 05:05
  • @Eric _"`short` is more specific than `long` because all shorts are convertible to long but not all longs are convertible to short."_ - thanks for making that 'click' for me. – CodeCaster Mar 04 '17 at 10:36
  • @CodeCaster: You're welcome. So, exercise: if the overloads only take double and decimal, and you pass integer 10, which one wins? – Eric Lippert Mar 04 '17 at 14:13
  • @Eric trick question, neither. The compiler tells me the call is ambiguous. I guess that's because an int can always be converted to both double and decimal, but you can't convert decimal to double nor vice versa. So they're both equal, unique overloads in that sense. – CodeCaster Mar 04 '17 at 17:51

2 Answers2

4

If you specify an integer literal in code without a suffix, according to the C# specification chapter 2.4.4.2, "Integer literals", it gets the type int, uint or long, depending on which type can hold its value.

So your 10 is an Int32s, and nothing will change that.

Now interestingly enough, if you remove the Int32 overload, the 10 is still an Int32s, but the overload with the smallest type that can hold that value is being called, in this case Int16.

I cannot find that quickly where this is specified, but you can see it in the resulting IL:

.method public hidebysig static void  Main(string[] args) cil managed
  {
    // 
    .maxstack  8
    IL_0000:  nop
    IL_0001:  ldc.i4.s   10
    IL_0003:  call       void Program::Foo(int16)
    IL_0008:  nop
    IL_0009:  ret
  } // end of method Program::Main

The compiler can do this, because 10 is a constant, hence the information is known at compile-time (as opposed to if it were a variable). As Eric Lippert states it:

It is implicitly convertible to all the built-in numeric types. Therefore when asked to make a choice of the best overload, overload resolution will first choose the exact match -- int -- if available. If not, then it will choose the unique most specific type available, if there is one. short is more specific than long because all shorts are convertible to long but not all longs are convertible to short.

See also:

Community
  • 1
  • 1
CodeCaster
  • 147,647
  • 23
  • 218
  • 272
  • What about assigning a decimal number to var? Will it be Int32 or Int16 – Sabyasachi Mishra Mar 03 '17 at 07:02
  • An integer can by definition not hold a decimal. See the "Integer literals" link. – CodeCaster Mar 03 '17 at 07:03
  • " I don't know why it prefers `Int16` over `Int64`, but probably because fewer bytes is better." This seems to be explained by Eric Lippert: You can put a `Int16` in a `Int64` with no problems. Vice versa can give a warning or more. – JHBonarius Mar 03 '17 at 09:37
  • @J.H.Bonarius but `10` is an `Int32`. The compiler could go the safe way, and choose the `Int64` overload, because you can always safely implicitly convert an `Int32` to an `Int64`. But then again, it would not compile if only the `Int16` overload was present. So the compiler knows math, or rather, knows the limits of built-in integral types and can deduct that `10` fits in an `Int16`. – CodeCaster Mar 03 '17 at 10:33
2

Default mapping is explained here

As Hans Passant says, what happend when you remove the Int32 overload is explained here

Foo.Bar(10) means 10 is const int instead of int. It makes sense now. const int goes to the smallest size the compiler can fix that int into i.e [short=Int16] in case of 10. Whereas int will got it's own size or larger i.e. long [= Int64] – basarat

_

The reasoning is: a constant integer is implicitly convertible to any integral type that it will fit into, therefore all three methods are applicable. We must now determine which of the three applicable methods is best. It is the one with the most specific parameter type. Type X is more specific than type Y if it is true that "all X can be converted to Y but not all Y can be converted to X". That is, Giraffe is more specific than Animal because all Giraffes are Animals but not vice versa. [short=Int16] is more specific than long[=Int64], so it wins. – Eric Lippert

Eric probably knows about this, as he was on the Microsoft C# language development team...

Community
  • 1
  • 1
JHBonarius
  • 10,824
  • 3
  • 22
  • 41