views:

118

answers:

2

I have the following scenario:

class Foo { }

class Foo<T> : Foo { }

And then two methods

void DoStuff(Foo foo) 
{
     DoStuffImpl(foo);
}

void DoStuffImpl(Foo foo) 
{ 
     Console.WriteLine("A");
}    
void DoStuffImpl<T>(Foo<T> foo) 
{ 
     Console.WriteLine("B");
} 

void Main() 
{
     DoStuff(new Foo<int>()); // prints A
}

(note, the code was written in the browser, but describes the situation I'm facing)

How can I get it to call the generic method, and print B?

Can this be done at all without reflection? I have some ideas on how it could be done with reflection, but I'm looking for a cleaner solution if one exists.

Note: I can't make DoStuff generic because this will be used with WCF and open generic types are not allowed.

+11  A: 

(I assume you already understand why this is happening. If not, read my overload resolution article and let me know if it's still unclear.)

If you're using C# 4 you could use dynamic typing:

void DoStuff(Foo foo) 
{
    dynamic d = foo;
    DoStuffImpl(d);
}

Note how this doesn't just have a dynamic parameter - the idea is that by restricting foo to be of type Foo or a subclass, we'll always have a valid DoStuffImpl to call... it's just that the best method will be determined at execution time, not compile time.

If you're stuck in pre-C# 4, you could potentially do it with double dispatch:

class Foo
{
    public virtual void CallStuffImpl(FooImplType x)
    {
        x.DoStuffImpl(this);
    }
}

class Foo<T> : Foo
{
    public override void CallStuffImpl(FooImplType x)
    {
        // Looks like it's redundant, but isn't! "this" is
        // known to be Foo<T> rather than Foo
        x.DoStuffImpl(this);
    }
}

Then:

void DoStuff(Foo foo) 
{
    foo.CallStuffImpl(this); // Let it dispatch appropriately
}
Jon Skeet
+1, Cool, had no idea dynamic could be used for overload resolution.
Kirk Woll
Thanks Jon. Yes, I understand why it's happening. Not using C# 4.0 yet, but this would be a good reason to switch to it if this would work!
legenden
Awarded for the pre C# 4.0 workaround. That's pretty slick.
legenden
+13  A: 

Overload resolution is performed at compile time. When "DoStuff" is compiled, it has already decided which version of DoStuffImpl to call, and it decides that based on the information available at compile time, not the information available at runtime.

There are four kinds of method dispatching in C#:

  • static dispatching chooses a static method at compile time. At runtime, the chosen method is called.

  • instance dispatching chooses an instance method at compile time. At runtime, the chosen method is called. (This is the form of dispatching used on non-virtual instance methods and on virtual methods called with "base.")

  • virtual dispatching chooses an instance method at compile time. At runtime, the most overridding version of that method on the runtime type of the object is called.

  • dynamic dispatching does nothing at compile time (*). At runtime the compiler starts up again and does the compile-time analysis at runtime, and generates fresh new code that is what would have been written had you gotten it right at compile time in the first place. This is every bit as expensive as it sounds; fortunately the result is cached so that on the second call, you don't get all the cost of analysis and codegen again.

Dynamic dispatching is only available in C# 4, or any version of VB.

(*) This is not quite true; there are some circumstances in which the compiler can do analysis at compile time even if the arguments to the method are dynamic. The details are complicated.

Eric Lippert
Thanks Eric, looks like it's time for us to move on to C# 4.0/VS2010, dynamic dispatch seems like the cleanest solution.
legenden
Quote: "the compiler starts up again". That's just a figure of speech for what the DLR does, right?
Hans Passant
@Hans: It's not the DLR per se; it's the C# binder implementing a DLR interface. (In particular I don't believe it's available on dlr.codeplex.com.)
Jon Skeet
<quote>The details are complicated</quote> Do I smell a blog post, or maybe an entire series?
Ben Voigt
@Hans: OK, *a* compiler starts up again. The C# runtime binder is a port of the relevant code from the command-line compiler libraries.
Eric Lippert
@Ben: See Chris Burrows's blog for the series.
Eric Lippert