Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Two-step method resolution with inheritance and generic constraints

I've encountered something quite surprising when using generic constraints with inheritance. I have an overloaded methods Foo that differ with parameter - either base or derived class instance. In both cases it's generally just passing the instance to the second pair of overloaded methods - Bar.

When I call Foo with base class instance, Bar overload for the base class is called. When I call Foo with derived class instance, Bar overload for the derived class is called. This is clear and expected.

But when I tried to merge Foo methods into single one GenericFoo that use generics and constraints, methods are resolved differently - T is resolved correctly, but only base-class overload of Bar is called.

public class Animal { }
public class Cat : Animal { }

public class AnimalProcessor
{
    public static void Foo(Animal obj)
    {
        Console.WriteLine("Foo(Animal)");
        Bar(obj);
    }

    public static void Foo(Cat obj)
    {
        Console.WriteLine("Foo(Cat)");
        Bar(obj);
    }

    // new generic method to replace the two above
    public static void GenericFoo<T>(T obj)
        where T : Animal
    {
        Console.WriteLine("Foo(generic)");
        Bar(obj);
    }

    public static void Bar(Animal obj)
    {
        Console.WriteLine("Bar(Animal)");
    }

    public static void Bar(Cat obj)
    {
        Console.WriteLine("Bar(Cat)");
    }
}

Testing code - two first cases for non-generic old methods, two last for new generic method.

Console.WriteLine("Animal()");
AnimalProcessor.Foo(new Animal());
Console.WriteLine();

Console.WriteLine("Cat()"); 
AnimalProcessor.Foo(new Cat());
Console.WriteLine();

Console.WriteLine("Animal()");
AnimalProcessor.GenericFoo(new Animal());
Console.WriteLine();

Console.WriteLine("Cat()"); 
AnimalProcessor.GenericFoo(new Cat());
Console.ReadLine();

And the result - note the difference in type resolved in Bar:

Animal()
Foo(Animal)
Bar(Animal)

Cat()
Foo(Cat)
Bar(Cat)

Animal()
Foo(generic)
Bar(Animal)

Cat()
Foo(generic)
Bar(Animal)

It looks like the compiler binds all calls from GenericFoo to the least specific overload, even if all more specific-typed calls are known at compile time. Why is that, what is the reason for such behaviour? Which part of specs defines this?

like image 391
NOtherDev Avatar asked Nov 22 '11 19:11

NOtherDev


1 Answers

Per OP's request, comment re-posted as answer:

Generics are not templates. Generic methods are compiled once and their behavior is for the 'most generic' case (in this case, Animal.) This is different from C++ style templating, where the template is compiled separately for each specialization by type.

like image 74
Dan Bryant Avatar answered Sep 28 '22 16:09

Dan Bryant