views:

871

answers:

3

Can anyone tell me why this code behaves the way it does? See comments embedded in the code...

Am I missing something really obvious here?

using System;
namespace ConsoleApplication3
{
    public class Program
    {
        static void Main(string[] args)
        {
            var c = new MyChild();
            c.X();
            Console.ReadLine();
        }
    }

    public class MyParent
    {
        public virtual void X()
        {
            Console.WriteLine("Executing MyParent");
        }
    }

    delegate void MyDelegate();

    public class MyChild : MyParent
    {
        public override void X()
        {
            Console.WriteLine("Executing MyChild");
            MyDelegate md = base.X;

            // The following two calls look like they should behave the same,
            //  but they behave differently!    

            // Why does Invoke() call the base class as expected here...
            md.Invoke();

            // ... and yet BeginInvoke() performs a recursive call within
            //  this child class and not call the base class?
            md.BeginInvoke(CallBack, null);
        }

        public void CallBack(IAsyncResult iAsyncResult)
        {
            return;
        }
    }
}
+5  A: 

I don't have an answer yet, but I have what I believe to be a slightly clearer program to demonstrate the oddity:

using System;

delegate void MyDelegate();

public class Program
{
    static void Main(string[] args)
    {
        var c = new MyChild();
        c.DisplayOddity();
        Console.ReadLine();
    }
}

public class MyParent
{
    public virtual void X()
    {
        Console.WriteLine("Executing MyParent.X");
    }
}

public class MyChild : MyParent
{
    public void DisplayOddity()
    {
        MyDelegate md = base.X;

        Console.WriteLine("Calling Invoke()");
        md.Invoke();                // Executes base method... fair enough

        Console.WriteLine("Calling BeginInvoke()");
        md.BeginInvoke(null, null); // Executes overridden method!
    }

    public override void X()
    {
        Console.WriteLine("Executing MyChild.X");
    }
}

This doesn't involve any recursive calls. The result is still the same oddity though:

Calling Invoke()
Executing MyParent.X
Calling BeginInvoke()
Executing MyChild.X

(If you agree that this is a simpler repro, feel free to replace the code in the original question and I'll remove it from my answer :)

To be honest, this looks like a bug to me. I'll dig around a bit more.

Jon Skeet
It looks like a bug with the internal code generated for BeginInvoke. Looking at the stacktrace of the 2nd call, confirms the 'correctness' of the method info in the delegate (still MyParent.X).
leppie
Another oddity is, why is Remoting being used for an async call? I really thought it would just use a simple thread or threadpool.
leppie
Where do you see Remoting coming in?
Jon Skeet
Place a breakpoint on the derived method. Compare that to a normal threadpool or threaded call. There seems to be a lot of 'remoting' stuff in there.
leppie
Oh yes. Found out this about it: http://blogs.msdn.com/cbrumme/archive/2003/07/14/51495.aspx - which also talks about virtual/non-virtual stuff...
Jon Skeet
A: 

Maybe not the answer you are looking for, but this seems to work:

ThreadPool.QueueUserWorkItem(x => md());

or

new Thread(() => md()).Start();

But you will need to do your own accounting :(

leppie
+1  A: 

While Delegate.Invoke calls the delegate method directly, Delegate.BeginInvoke internally uses ThreadPool.QueueUserWorkItem( ). md.Invoke() was only able to call base.X because a base class's methods are accessible within the derived class through the base keyword. Since the delegate started by the thread pool is external to your class, the reference to its X method is subjected to overloading, just like the code below.



    public class Program
    {
        static void Main(string[] args)
        {
            MyChild a = new MyChild();
            MyDelegate ma = new MyDelegate(a.X);

            MyParent b = new MyChild();
            MyDelegate mb = new MyDelegate(b.X);

            ma.Invoke();
            mb.Invoke();
            ma.BeginInvoke(CallBack, null);
            mb.BeginInvoke(CallBack, null); //all four calls call derived MyChild.X

            Console.ReadLine();
        }

        public static void CallBack(IAsyncResult iAsyncResult)
        {
            return;
        }
    }

Debug into .NET Framework code: http://blogs.msdn.com/sburke/archive/2008/01/16/configuring-visual-studio-to-debug-net-framework-source-code.aspx

foson