8

I come from a C++ background and I am relatively new to C#. Currently, I am trying to write two print functions, the first of which accepts a generic array parameter (and prints the items of the array to the command line) and a second one which accepts a generic primitive parameter (and invokes its ToString() method). Here's my code:

using System;

namespace OverloadResolution
{
  class Program
  {
    static void Main(string[] args)
    {
      string[] foo = new string[] { "A", "B", "C" };
      Extensions.PrintMe(foo);
      Extensions.PrintMe("Hello world");
      Console.ReadLine();
    }
  }

  public static class Extensions
  {
    public static void PrintMe<T>(T[] elm)
    {
      Console.WriteLine("PrintMe<T>(T[] elm)");
      Console.WriteLine(string.Join("", elm));
    }

    public static void PrintMe<T>(T elm)
    {
      Console.WriteLine("PrintMe<T>(T elm)");
      Console.WriteLine(elm.ToString());
    }
  }
}

Everything works as expected and the correct overloads are chosen. However, if I change my code as follows:

using System;

namespace OverloadResolution
{
  class Program
  {
    static void Main(string[] args)
    {
      string[] foo = new string[] { "A", "B", "C" };
      Extensions.PrintMe2(foo); // <<< Note the PrintMe2 function
      Extensions.PrintMe2("Hello world"); // <<< Note the PrintMe2 function
      Console.ReadLine();
    }
  }

  public static class Extensions
  {
    public static void PrintMe2<T>(T elm)
    {
      PrintMe(elm);
    }

    public static void PrintMe<T>(T[] elm)
    {
      Console.WriteLine("PrintMe<T>(T[] elm)");
      Console.WriteLine(string.Join("", elm));
    }

    public static void PrintMe<T>(T elm)
    {
      Console.WriteLine("PrintMe<T>(T elm)");
      Console.WriteLine(elm.ToString());
    }
  }
}

the type information seems to get lost when calling the PrintMe2 function because, internally, the second PrintMe function is invoked in both cases. Are there any special rules which apply in this case? Or am I missing something? I use C#7.0 and .NET framework 4.7. I should note that I already learned that the use of C# generics is quite limited as compared to C++ templates...

2
  • Possible duplicate of Generic overload resolution Commented Nov 6, 2018 at 7:17
  • 1
    @DragandDrop: It's not quite a duplicate of that. Commented Nov 6, 2018 at 7:17

2 Answers 2

6

When PrintMe2 is being compiled, we have to perform overload resolution to determine which PrintMe method is being called, and we have to emit the correct method token into PrintMe2s IL to identify that method.

At the point at which PrintMe2 is being compiled, we do not know the nature of its T type parameter. It could be anything. The only version of PrintMe that is similarly unrestricted is PrintMe<T>(T elm). Therefore, we emit the method token for that specific method into our IL.

Generics are different to templates, most notably because they are compiled separately from any code that may make use of them. We have to make decisions at their compile time, and those decisions have to be valid for all possible type parameters (subject to any type constraints applied to the class/method in which the type parameter(s) are declared)

If you want a method in which overload resolution only takes place based on the runtime type of the passed in parameter, you can use dynamic, but that's quite ugly and brings in lots of overheads. It may also lead to runtime errors in no specific overload is most specific for some use cases.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for this good explanation - especially for pointing out the difference to templates.
@Marcel - yes, I think it's quite instructive to look at templates in C++ and generics in both Java and .NET to see how the same basic concept can be expressed and implemented in very different ways. They lead to different trade-offs. .NET, of course, was aiming for cross-language support for generics and so could not make the same decisions as were made for C++.
1

You can cast the argument to pseudo-type dynamic to force C# compiler to select the most specific overload based on the actual value of the argument, like this:

PrintMe((dynamic)elm);

However, if you inspect the generated IL code, this comes with a runtime penalty, so you should use the dynamic keyword sparingly.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.