7

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;
        }
    }
}
Joel Coehoorn
  • 399,467
  • 113
  • 570
  • 794
  • I have not tried this, or was aware there was a problem, but I can see a lot of issues coming from this. Perhaps someone can explain :) – leppie Oct 23 '08 at 12:22

3 Answers3

5

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
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 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 Oct 23 '08 at 13:03
  • 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 Oct 23 '08 at 14:44
  • Where do you see Remoting coming in? – Jon Skeet Oct 23 '08 at 16:30
  • 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 Oct 23 '08 at 17:59
  • 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 Oct 23 '08 at 19:36
1

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
  • 10,037
  • 2
  • 35
  • 53
0

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
  • 115,091
  • 17
  • 196
  • 297