0

In the code below I'm (wrongfully apparently) expecting that method of the more specific (derived if you will) type, to be bound/called:

using System;

public class Program
{   
    public static int integer = 52;
    public static Program program = new Program();
        
    public static void Main()
    {
        TestReturn(program); // this works as expected all the way
        TestReturn(integer); // 1. this not quite...
    }
    
    public static T TestReturn<T>(T t) // 2. TestReturn<Int32> all good...
    {
        Console.WriteLine("In TestReturn<" + typeof(T) + ">");
        return (T)Extensions.Undefined(t); // 3. wrong call
    }
}

public static class Extensions
{
    public static object Undefined(this object t) // 4. this is called, instead of (5)
    {
        Console.WriteLine("In Undefined(obj)");
        return null;
    }
    
    public static int Undefined(this int b) // 5. this is expected to be called
    {
        Console.WriteLine("In Undefined(int)");
        return int.MinValue;
    }
}

Output:

In TestReturn<Program>
In Undefined(obj)
In TestReturn<System.Int32>
In Undefined(obj)
Run-time exception (line 17): Object reference not set to an instance of an object.  

Can someone tell why is this happening and how to do it in order to work as I intended?

mireazma
  • 526
  • 1
  • 8
  • 20
  • 2
    Since you don't have any restriction on the generic type the compiler has to pick the one method that would work for every possible `T` – UnholySheep Oct 10 '20 at 22:36
  • 1
    I guess it's worth pointing out that if C# was an interpreted language, it would probably work as expected, but as UnholySheep points out, at compile time the compiler needs choose which extension method should be called by `TestReturn`, which, without a `where` clause will default to the most generic. – Alexander Høst Oct 10 '20 at 22:51
  • 1
    https://dotnetfiddle.net/UojDTv will defer the decision as to which method to call until runtime. Which appears to be what you want. – mjwills Oct 10 '20 at 23:54
  • This is my take away: compiler can't resolve `T` at compile time. Even if the code has the calls with concrete types. The most it can do is to make a general idea about `T`: _whether_ `T` is assignable from the type of any of the calls, based on constraints. – mireazma Oct 11 '20 at 11:13

1 Answers1

1

Taking from the C# Programming guide:

The basic definition of extension method says:

The intermediate language (IL) generated by the compiler translates your code into a call on the static method.

How the extension methods are binded at run time:

An extension method with the same name and signature as an interface or class method will never be called. At compile time, extension methods always have lower priority than instance methods defined in the type itself.

When the compiler encounters a method invocation, it first looks for a match in the type's instance methods. If no match is found, it will search for any extension methods that are defined for the type, and bind to the first extension method that it finds.

Extending predefined types can be difficult with struct types because they're passed by value to methods. That means any changes to the struct are made to a copy of the struct. Those changes aren't visible once the extension method exits.

As per documentation, and also mentioned in the comments by @UnholySheep and @Alexander Høst, at the compile time of your Program class, the compiler choses the more generic type to suffice the general approach of your "T" type. In other words, the extension method covering an "object" is chosen by the compiler as "T" might be anything.

The proof of concept it that the following code won't compile:

namespace ConsoleForEverything
{
    public class Program
    {
        public static int integer = 52;    
        public static Program program = new Program();

        public static void Main()
        {
            TestReturn(integer); // 1. this not quite...
        }

        public static T TestReturn<T>(T t) // 2. TestReturn<Int32> all good...
        {
            Console.WriteLine("In TestReturn<" + typeof(T) + ">");
            return (T)Extensions.Undefined(t); // 3. wrong call
        }
    }

    public static class Extensions
    {
        public static int Undefined(this int b) // 5. this is expected to be called
        {
            Console.WriteLine("In Undefined(int)");
            return int.MinValue;
        }
    }
}
Bruno
  • 924
  • 9
  • 20
  • `and how to do it in order to work as I intended?` See my above comment for that. – mjwills Oct 10 '20 at 23:59
  • In the official documentation, 3rd quoted paragraph is of interest, specifically "bind to the first extension method that it finds". So I'll just write the `int` parameter method above the `object` parameter one :) "the extension method covering an "object" is chosen by the compiler as "T" might be anything" this actually explains all. – mireazma Oct 11 '20 at 11:27
  • `When the compiler encounters a method invocation, it first looks for a match in the type's instance methods. If no match is found, it will search for any extension methods that are defined for the type, and bind to the first extension method that it finds.` All this polymorphism stuff happens exclusively at compile time. For run time, i.e. `dynamic` an error is thrown if no method is found matching _exactly_ the type. – mireazma Oct 12 '20 at 21:04
  • Yes, I agree, as an example provided [here](https://dotnetfiddle.net/UojDTv), however we must pay attention to the fact that in the provided example, we are not calling your TestReturn method directly, instead, it calls the extension method Inside of this method, meaning that at runtime the IL take the decision based on the type that is calling Extensions.Undefined(). – Bruno Oct 13 '20 at 08:10