views:

271

answers:

4

Hello *,

experimenting with Visitor pattern and generic method I found a kind of discrepancy in C#.NET. AFAIK C# compiler prefers an explicit overload to a generic method, therefore the following code:

public abstract class A
{
    public abstract void Accept(Visitor v);
}

public class B : A
{
    public override void Accept(Visitor v)
    { v.Visit(this); }
}

public class C : A
{
    public override void Accept(Visitor v)
    { v.Visit(this); }
}

public class D : A
{
    public override void Accept(Visitor v)
    { v.Visit(this); }
}

public class Visitor
{
    public void Visit(B b)
    { Console.WriteLine("visiting B"); }

    public void Visit(C c)
    { Console.WriteLine("visiting C"); }

    public void Visit<T>(T t)
    { Console.WriteLine("visiting generic type: " + typeof(T).Name); }
}

class Program
{

    static void Main()
    {
        A b = new B();
        A c = new C();
        A d = new D();

        Visitor v = new Visitor();

        b.Accept(v);
        c.Accept(v);
        d.Accept(v);
    }
}

The output produced is (as expected):

visiting B
visiting C
visiting generic type: D

However this Visitor pattern implementation does not allow to exchange the Visitor class. Introducing an abstract class VisitorBase and forwarding the call to the overloads produces smth. unexpected for me....

public abstract class A
{
    public abstract void Accept(VisitorBase v);
}

public class B : A
{
    public override void Accept(VisitorBase v)
    { v.Visit(this); }
}

public class C : A
{
    public override void Accept(VisitorBase v)
    { v.Visit(this); }
}

public class D : A
{
    public override void Accept(VisitorBase v)
    { v.Visit(this); }
}

public abstract class VisitorBase
{
    public abstract void Visit<T>(T t);
}

public class Visitor : VisitorBase
{
    protected void VisitImpl(B b)
    { Console.WriteLine("visiting B"); }

    protected void VisitImpl(C c)
    { Console.WriteLine("visiting C"); }

    protected void VisitImpl<T>(T t)
    { Console.WriteLine("visiting generic type: " + typeof(T).Name); }

    public override void Visit<T>(T t)
    {
        VisitImpl(t); //forward the call to VisitorImpl<T> or its overloads
    }
}

class Program
{

    static void Main()
    {
        A b = new B();
        A c = new C();
        A d = new D();

        VisitorBase v = new Visitor();

        b.Accept(v);
        c.Accept(v);
        d.Accept(v);
    }
}

Now the output is:

visiting generic type: B
visiting generic type: C
visiting generic type: D

Do generic methods only prefer generic methods? Why are no explicit overloads called?

Many thanks,
Ovanes

A: 

Generics are a compiler feature, so only information available at compile time is used to determine what method should be called. What you are doing would require at runtime to determine what the actual type of the variable is. The compiler only knows that variable b is of type A, c is of type A, and d is of type A. It's picking the best overload, which is the generic one, as there is no method that takes A.

Darren Kopp
But your argument does not fit here. When calling VisitorBase.Visit<T>(...) I assume that compiler knows what T is. Or does it make a type erasure? IMO calling a generic method which forwards the call to generic method should preserve all type information. As I see it in my example it gets lost. Why? In C++ for example no virtual function templates are allowed. Why are these allowed in C# and handled wrong?
ovanes
In C# to make any assumptions on a generic type argument (and you need to make one, when choosing the overload) a generic argument has to have a generic type constraint. Visit<T> in your code does not have any type constraints, so compiler could, but is not permitted to resolve the overload.
George Polevoy
@ovanes: you statically typed your variables to A though. No information is lost, it's still the same object, but you are assuming you are dealing with type A, rather than type B. If you want the compiler to correctly resolve, then either when declaring the variable, use the type it wants. The compiler will attempt to pick the most specific signature falling back to the next least specific signature until a method is found.
Darren Kopp
+5  A: 

Overloading is done statically, so when you call VisitImpl(t), the compiler must pick the single best overloaded method that this call represents (if there is one). Since the type parameter T could be anything, the only method which is compatible is the generic method, and therefore all calls from Visit<T>(T t) call into VisitImpl<T>(T t).

EDIT

It looks like you may be coming from a C++ background, so perhaps it's worth noting that C++ templates are very different from C# generics; in particular, there's no such thing as specialization in C#, which may be why the behavior you see is unexpected. The C# compiler does not emit different code for the different types at which a generic method may be called (that is, the C# compiler calls the same generic method when you call Visit(1) and Visit("hello"), it does not generate specializations of the method at types int and string). At runtime, the CLR creates type specific methods, but this happens after compilation and cannot affect overload resolution.

EDIT - even more elaboration

C# does prefer non-generic methods to generic methods when the non-generic method is statically known to be applicable.

The C# compiler will pick a single method to call at any given call-site. Forget about overloading entirely, and give your methods each a different name; which of those renamed methods can be called at the call-site in question? Only the generic one. Therefore, even when the three names collide and overload resolution kicks in, that is the only overload which is applicable at that site, and is the method chosen.

kvb
please read my comment to Darren Kopp.
ovanes
@kvb: No the behavior he sees is unexpected. Non-generic methods are supposed to be preferred to generics at compile time. https://connect.microsoft.com/VisualStudio/feedback/details/522202/c-3-0-generic-overload-call-resolution-from-within-generic-function
Joel Etherton
Yes, I know that C#.NET does not allow the template specialization, but the statement here that .NET prefers explicit overloads to generic methods is not true. Can you point me to the part of CLI standard or C# standard, which would explain the behavior? Otherwise I think it is a bug.
ovanes
@Joel - read the comment left on that connect issue - it's based on a misunderstanding of the rule and is not a bug.
kvb
@kvb: That comment is what I was basing my comment on actually.
Joel Etherton
It is not a bug. When `Visitor.Visit<T>` is compiled, the compiler has *no information whatsoever* about `T`, not even whether it is a reference type or a value type. So compiler has no choice but call `VisitImpl<T>`. Remember, not only does the compiler not emit different code for instantiations of a generic method, there actually are no instantiations at compile time; there is only the single method `Visit<T>`.
Anton Tykhyy
A: 

As I understand it, and I could be very wrong, at compile time the generic function visit actually performs a sort of unboxing of the original type. While we can logically see that the types should run through at compile time, the C# compiler can't make it through the Visit function to the VisitImpl function while holding the types, so the original b.visit(v) is considered unboxed at compile. Given this, it must route through the generic for all types that match when the Visit method is called.

EDIT: To clarify what I mean because I just read my own crap:

The compiler holds the link for b.Visit as a generic call. It fits and is labeled generic. The compiler holds separate links for Visit->VisitImpl as typed and/or generic methods as necessary. The compiler can not hold a link from b.Visit (as generic) -> VisitImpl as typed. Since the path from b.Visit() -> VisitImpl must go through a generic, it holds it as a generic type and so the generic VisitImpl is preferred.

Joel Etherton
Thanks for the explation, together with the link you provided in kvb's post I understand how it is handled. But this is really not nice!!!
ovanes
Note that the compiler's behavior here has nothing to do with boxing/unboxing. Boxing stores value types in objects, but in this case all of the types involved are reference types.
kvb
@kvb: I used the term loosely because it was the best way to describe it. It is not semantically correct, but the idea behind the link is the imporant part. The compiler is unable to hold the link beyond the original Generic constraint. Hence, why I put the edit in there to hopefully clarify the description that I already knew was muddled. But thanks for pointing out the obvious.
Joel Etherton
+1  A: 

It seems you're confusing overloading and overriding.

Overloading is when you provide multiple methods with the same name, that differ in parameter types:

class Foo
   |
   +- void Qux(A arg)
   +- void Qux(B arg)
   +- void Qux(C arg)

Overriding is when you provide multiple implementations of the same (virtual) method:

class Foo                  class Bar : Foo             class Baz : Foo
   |                          |                           |
   +- virtual void Quux()     +- override void Quux()     +- override void Quux()

C# performs single dispatch:

  • The overload of an invoked method is determined at compile-time.

  • The implementation of an overridden method is determined at run-time.

The visitor pattern exploits the latter by dispatching the method call to the right implementation of the Visit method. In languages with multiple dispatch, the visitor pattern is not needed because the right overload is chosen at run-time.

dtb