Generic overload resolution

2019-01-15 09:16发布

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.

2条回答
祖国的老花朵
2楼-- · 2019-01-15 09:44

(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
}
查看更多
叛逆
3楼-- · 2019-01-15 09:49

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.

查看更多
登录 后发表回答