MultiCast delegate order

T

tshad

I have a small example of a multiCast program that will invoke 4 delegates
and write to the screen. Each delegate will write 2 lines (begin and end
messages)

I would have thought that I would get the whole delegate finished before the
next one starts. Do MultiCast Delegates run asynchronously?

My code:
**************************************
using System;
using System.Collections.Generic;
using System.Text;

namespace ConsoleApplication1
{
public class DeligateClass
{
public DeligateClass()
{
}
// Declare a delegate
public delegate void MessageHandler(string message);
// The use of the delegate.

public void Process(MessageHandler handler)
{
if (handler != null)
handler("Begin Message");
if (handler != null)
handler("End Message");
}
}

public class FunctionClass
{
public FunctionClass()
{
}

//This method will show alert message to user
public static void AlertMessage(string s)
{
Console.WriteLine("Inside AlertMessage \n " + s);
}

//This method will write output to console

public static void ConsoleMessage(string s)
{
Console.WriteLine("Inside ConsoleMessage\n " + s);
}
}

class Program
{
static void Main(string[] args)
{
DeligateClass dc = new DeligateClass();
DeligateClass.MessageHandler ll = null;
ll += new
DeligateClass.MessageHandler(FunctionClass.AlertMessage);

//Calling a delegate
ll += FunctionClass.ConsoleMessage;
ll += FunctionClass.ConsoleMessage;
ll += FunctionClass.ConsoleMessage;
dc.Process(ll);
Console.ReadLine();
}
}
}
**************************************
The Result:
********************
Inside AlertMessage
Begin Message
Inside ConsoleMessage
Begin Message
Inside ConsoleMessage
Begin Message
Inside ConsoleMessage
Begin Message
Inside AlertMessage
End Message
Inside ConsoleMessage
End Message
Inside ConsoleMessage
End Message
Inside ConsoleMessage
End Message
******************************

It does all the Begins (in order) so all the delegates start (in order).
Then the Ends are done (in order).

Why is it not:
**************************
Inside AlertMessage
Begin Message
Inside AlertMessage
End Message
Inside ConsoleMessage
Begin Message
Inside ConsoleMessage
End Message
Inside ConsoleMessage
Begin Message
Inside ConsoleMessage
End Message
Inside ConsoleMessage
Begin Message
Inside ConsoleMessage
End Message
***************************

Thanks,

Tom
 
J

Jon Skeet [C# MVP]

It does all the Begins (in order) so all the delegates start (in order).
Then the Ends are done (in order).

Yes. That's exactly what you've told it to do in this code:

if (handler != null)
handler("Begin Message");
if (handler != null)
handler("End Message");

That's basically doing (pseudo code):

foreach (single-cast-handler sch in handler)
{
sch("BeginMessage");
}
foreach (single-cast-handler sch in handler)
{
sch("EndMessage");
}

There's nothing asynchronous going on here - you're calling all the
handlers with "begin", then you're calling all the handlers with
"end".
If you want to loop through each handler, calling begin then end,
you'll need to look at Delegate.GetInvocationList.

Jon
 
T

tshad

Jon said:
Yes. That's exactly what you've told it to do in this code:

if (handler != null)
handler("Begin Message");
if (handler != null)
handler("End Message");

That's basically doing (pseudo code):

foreach (single-cast-handler sch in handler)
{
sch("BeginMessage");
}

foreach (single-cast-handler sch in handler)
{
sch("EndMessage");
}

There's nothing asynchronous going on here - you're calling all the
handlers with "begin", then you're calling all the handlers with
"end".

How am I calling all handlers with "begin" first?

I don't quite see where the loops come from.

I am calling delegate 1 (alert) - which does an alert("Begin Message"), then
an alert("End Message"). Delegate 2 does a Console.WriteLine("Begin
Message") and then Console.WriteLine("Begin Message"). Why do all the
begins get done first? If it is not Asynchronous, would not both the Alerts
be done first since they are together in the Process function?
If you want to loop through each handler, calling begin then end,
you'll need to look at Delegate.GetInvocationList.

What is different about GetInvocatoinList then the way I did it?

Thanks,

Tom
 
J

Jon Skeet [C# MVP]

Jon Skeet [C# MVP] wrote:


Yes. That's exactly what you've told it to do in this code:
if (handler != null)
    handler("Begin Message");
if (handler != null)
    handler("End Message");
That's basically doing (pseudo code):
foreach (single-cast-handler sch in handler)
{
    sch("BeginMessage");
}
foreach (single-cast-handler sch in handler)
{
    sch("EndMessage");
}
There's nothing asynchronous going on here - you're calling all the
handlers with "begin", then you're calling all the handlers with
"end".

How am I calling all handlers with "begin" first?

With this line of code:
handler("Begin Message");

That is calling
I don't quite see where the loops come from.

I am calling delegate 1 (alert) - which does an alert("Begin Message"), then
an alert("End Message").

No you're not. You're calling *all* the delegates referenced by the
"handler" variable with "Begin Message". That's what the line
handler("BeginMessage") does.

You're then calling *all* the delegates referenced by the "handler"
variable with "End Message".

Note that you're calling Process *once*, with a multicast delegate.
How could the "end" part possibly come before another "begin" part?
What is different about GetInvocatoinList then the way I did it?

Think of handler as h1 + h2 + h3 + h4, where those are the individual
handlers. You've currently got:

handler("Begin Message");
handler("End Message");

That's equivalent to:

h1("Begin Message");
h2("Begin Message");
h3("Begin Message");
h4("Begin Message");
h1("End Message");
h2("End Message");
h3("End Message");
h4("End Message");

What you want is:

h1("Begin Message");
h1("End Message");
h2("Begin Message");
h2("End Message");
h3("Begin Message");
h3("End Message");
h4("Begin Message");
h4("End Message");

You can't do that by calling handler directly, because that will just
call h1, h2, h3 and h4 with the same message without giving you a
chance to do anything in between. Calling GetInvocationList
effectively lets you get h1, h2, h3 and h4 individually, so you can
call them however you like.

Jon
 
T

tshad

Peter said:
[...]
There's nothing asynchronous going on here - you're calling all the
handlers with "begin", then you're calling all the handlers with
"end".

How am I calling all handlers with "begin" first?

I don't quite see where the loops come from.

It's implicit when you invoke the delegate. The framework provides
the loop on your behalf.
I am calling delegate 1 (alert) - which does an alert("Begin
Message"), then
an alert("End Message"). Delegate 2 does a Console.WriteLine("Begin
Message") and then Console.WriteLine("Begin Message").

In your own code, do you see any place where you explicitly call
"delegate 1" at a different place from where you call "delegate 2"? No.
You don't call either of them explicitly at all. You simply
invoke the combined delegates when you call the Process() method.
Why do all the
begins get done first? If it is not Asynchronous, would not both the
Alerts
be done first since they are together in the Process function?

You only call Process() once, and Process() only invokes the delegate
with "Begin" once and with "End" once. So how could it alternate? It
_must_ do all the "Begin" invocations during the one call with "Begin"
that
you have, and likewise the "End" invocations.
What is different about GetInvocatoinList then the way I did it?

Using GetInvocationList(), you get access to each delegate within the
MulticastDelegate individually, which then allows you to invoke each
one individually, alternating between "Begin" and "End". For example:

public void Process(MessageHandler handlerMulticast)
{
foreach (Delegate delegate in
handlerMulticast.GetInvocationList()) {
MessageHandler handler = (MessageHandler)delegate;

handler("Begin");
handler("End");
}
}

I think I see now.

I changed my Process slightly just to see what it was doing.

**********************************
public void Process(MessageHandler handler)
{
Console.WriteLine("Inside Process");
Console.WriteLine("Handlers:");
foreach (Delegate d in handler.GetInvocationList())
Console.WriteLine(" Invocation: " + d.Method.Name);
Console.WriteLine("Before Begin Handler");
if (handler != null)
handler("Begin Message");
Console.WriteLine("After Begin Handler");
Console.WriteLine("Before End Message");
if (handler != null)
handler("End Message");
Console.WriteLine("After End Message");
}
**********************************
And get the following result:
************************************
Inside Process
Handlers:
Invocation: AlertMessage
Invocation: ConsoleMessage
Invocation: ConsoleMessage
Invocation: ConsoleMessage
Before Begin Handler
Inside AlertMessage
Begin Message
Inside ConsoleMessage
Begin Message
Inside ConsoleMessage
Begin Message
Inside ConsoleMessage
Begin Message
After Begin Handler
Before End Message
Inside AlertMessage
End Message
Inside ConsoleMessage
End Message
Inside ConsoleMessage
End Message
Inside ConsoleMessage
End Message
After End Message
************************************

So what process is doing, as you say, is handling each line at a time (once
since proces is only called once) and then when it gets to handler, which it
knows as a multiclass delegate and will now execute the delegate list (in
this case 4 delegates). It then does the next set of statements until it
gets to the next handler and then process that list (4 of them). It then
does the last statement.

I assume that all delegates are really multiclass delegates with only 1
delegate in the list. The type of the handler is a delegate and a
collection of delegates.

If I don't add any more delegates to the delegate then it is only one
delegate.

I could have just as easily defined "ll" as:

DeligateClass.MessageHandler ll = new
DeligateClass.MessageHandler(FunctionClass.AlertMessage);

Now I have one delegate ll. Had I done dc.Process(ll) here, it would have
gone through the list of delegates (which in this case is one) for each
handler.

If I now do:

ll += new
DeligateClass.MessageHandler(FunctionClass.AlertMessage);

the ll delegate is really a list of delegates (which is now 2 delegates) and
dc.Process(ll) will again go through the delegate list (2 here) for each
handler.

So when you execute a delegate - you are always executing a list of
delegates.

A delegate and multiCast delegate are really synonymous.

Makes a little more sense now.

Thanks,

Tom
 
T

tshad

Peter said:
[...]
I assume that all delegates are really multiclass delegates with
only 1 delegate in the list. The type of the handler is a delegate
and a collection of delegates.
[...]

A delegate and multiCast delegate are really synonymous.

Yes. For hyste^H^H^H^Historical reasons, there is no non-multicast
Delegate type in C# (maybe not even in .NET, but I can't recall for
sure at the moment). All delegate types are potentially multicast.

For what it's worth, you may also want to keep in mind that delegates
are immutable. When you combine two delegates (using the + operator
or Combine() method), you get a whole new delegate that concatenates
the invocation lists from the original two. The original two
delegates remain unmodified.

So, for example:

delegate void MyDelegate();

void MethodA() { }

void MethodB()
{
MyDelegate delegate1 = MethodA;
MyDelegate delegate2 = delegate1;

delegate1 += MethodA;
}

At the end of MethodB(), delegate1 will have two delegate references
to MethodA in its invocation list, but delegate2 will still only have
one, since delegate2 is still referencing the original instance
assigned to delegate1, rather than the new one created in the final
statement of MethodB().
Is delegate2 refencing the instance or is it copying the delegate1 list to
delegate2 so that whatever happens to delegate1 doesn't happen to the list
that was copied to delegate2 because we are not referencing delegate1.
Delegate2 has a copy of Delegate1.

Thanks,

Tom
 
J

Jon Skeet [C# MVP]

tshad said:
Is delegate2 refencing the instance or is it copying the delegate1 list to
delegate2 so that whatever happens to delegate1 doesn't happen to the list
that was copied to delegate2 because we are not referencing delegate1.
Delegate2 has a copy of Delegate1.

Delegates are immutable, so there's nothing that could happen to
delegate1 to change anything. I don't actually know whether it's just
the invocation list which is copied, or a reference to the delegate
instance. I'd expect the former, but it doesn't make much difference
beyond garbage collection.
 
J

Jon Skeet [C# MVP]

Peter Duniho said:
Maybe I've misunderstood, but...why would you expect the former?

Because I misread the previous post. Oops. I thought Tom was asking
about what happened when Delegate.Combine was called due to +=.

No, simple assignment is just a reference copy like anything else.
 
T

tshad

Jon said:
Because I misread the previous post. Oops. I thought Tom was asking
about what happened when Delegate.Combine was called due to +=.

No, simple assignment is just a reference copy like anything else.

Now I am confused again.

When you say "reference copy" are to copying the reference to the multCast
list?

If this were the true, then I would assume (and could be wrong here) that if
I copied (added) delegate1 to delegate2 (which would have a MultiCast list
of 1 or more delegates), and than added or subtracted other delegates from
delegate1s' list - that would change delegate2s' list. But that is not what
happens.

If I have:

*********************************
MyDelegate delegate1 = MethodA;
MyDelegate delegate2 = delegate1;

delegate1 += MethodA;

Console.WriteLine();
foreach (Delegate a in delegate1.GetInvocationList())
Console.WriteLine("Delegate1 - " + a.Method.Name);

foreach (Delegate a in delegate2.GetInvocationList())
Console.WriteLine("Delegate2 - " + a.Method.Name);

*********************************
Here we are adding MethodA to delegate1. Then we copy delegate1 to
delegate 2. So delegate2 either has a pointer to delegate1 or a reference
to delegate1. So if I look at the Invocation list both would show 1 MethodA
method.

Then I add another MethodA to the delegate1 list.

If the delegate2 had a copy of the original delegate1 list instead of a
reference to delegate1, then I should still have only one method in
delegate2 (which is what appears to be the case in my following results).
If there was a reference to delegate1 than delegate2 should now show 2
MethodAs which it doesn't.

*************************************
Delegate1 - MethodA
Delegate1 - MethodA
Delegate2 - MethodA
*************************************

Am I missing something here?

Thanks,

Tom
 
T

tshad

Peter said:
Jon Skeet [C# MVP] wrote:
[...]
No, simple assignment is just a reference copy like anything else.

Now I am confused again.

When you say "reference copy" are to copying the reference to the
multCast
list?

No. The "reference copy" is a copy of the reference to the delegate
instance itself, not its list.

The invocation list is an internal data structure to the delegate,
and it never changes. The delegate instance itself is immutable, and
any operation (such as + or - operators) will create a whole new
delegate instance with a whole new invocation list that reflects
whatever change was indicated by the operation.

Put another way: assigning one delegate to another simply assigns the
reference of the delegate instance itself. Yes, this implicitly also
references the internal data structure (the invocation list), but that
instance of the internal data structure will never change. Any
operation that would produce a different version of the internal data
structure will create a new copy of the data structure, leaving the
original unchanged. Existing references to that original data
structure will continue to refer to the original unchanged data
structure, while targets of the operation that produce a new,
different version of the data structure wind up referencing a whole
new delegate instance, one that contains that new, different version
of the data structure.

So then let me see if I have this right.

If I make the following changes to my code so that I have 4 delegates
(1,2,3,4) with the following assignments:

*****************************************
1 MyDelegate delegate1 = MethodA; <---- this would create a new
delegate1 (call it delegate1.0)
2 MyDelegate delegate2 = delegate1;
3
4 delegate1 += MethodB; <---- this would create a new
delegate1 (call it delegate1.1)
5 delegate1 += delegate1; <---- this would create a new
delegate1 (call it delegate1.2)
6 MyDelegate delegate3 = delegate1;
7 delegate1 = MethodC; <---- this would create a new
delegate1 (call it delegate1.3)
8 MyDelegate delegate4 = delegate1;

foreach (Delegate a in delegate1.GetInvocationList())
Console.WriteLine("Delegate1 - " + a.Method.Name);

foreach (Delegate a in delegate2.GetInvocationList())
Console.WriteLine("Delegate2 - " + a.Method.Name);

foreach (Delegate a in delegate3.GetInvocationList())
Console.WriteLine("Delegate3 - " + a.Method.Name);

foreach (Delegate a in delegate4.GetInvocationList())
Console.WriteLine("Delegate4 - " + a.Method.Name);
*****************************************

When I change a delegate, the other delegate in essence becomes invisible
and there is no way to access it accept through another delegate I have
already assigned it to.

So at line 1, I have assigned MethodA to delegate1
At line 2, I have assigned delegate1 to delegate 2 (which will have MethodA
in its list)
At line 4, I have the original delegate1 (delegate1.0) that can never be
accessed again. But is still around and is used by delegate2. A new
delegate object delegate1.1 is now created. with MethodA and MethodB.
At line 5, another delegate1 is created (delegate1.2) and now delegate1.1 is
gone and would be taken out of the heap by the Garbage Collector since
nothing is accessing it.
At line 6, delegate1 (delegate1.2) is now assigned to delegate3.
At line 7, we now get a new delegate1 that we have assigned MethodC to. Now
delegate1.2 is invisible but is still there pointed at by delegate3.
At line 8, this new delegate (delegate1.3) is now assigned to delegate4.

Delegate1.1 is the only object that is completely gone since nothing is
pointing at it.
Delegate1.0 and delegate1.2 are still around, pointed at by other delegates.
Delegate1.3 is assigned to another delegate and can be assigned to more
until changed again.

The result is:

*********************************************
Delegate1 - MethodC <-- delegate 1.3
Delegate2 - MethodA <-- delegate 1.0
Delegate3 - MethodA <-- delegate 1.2
Delegate3 - MethodB <-- delegate 1.2
Delegate3 - MethodA <-- delegate 1.2
Delegate3 - MethodB <-- delegate 1.2
Delegate4 - MethodC <-- delegate 1.3
*********************************************

Thanks,

Tom
 
J

Jon Skeet [C# MVP]

When I change a delegate, the other delegate in essence becomes invisible
and there is no way to access it accept through another delegate I have
already assigned it to.

Assuming there are no other variables which reference the delegate,
that's right - and it's just like other reference type when it comes
to garbage collection.

Jon
 
T

tshad

Jon said:
Assuming there are no other variables which reference the delegate,
that's right - and it's just like other reference type when it comes
to garbage collection.
Right.

And as I think you mentioned, delegates are just like strings (immutable).
A whole new object is created when you change the object so that you have 2
objects and the 1st object will be garbage collected if nothing is
referenced it. But in either case, you will not be able to access the 1st
object directly.

Thanks,

Tom
 
T

tshad

Jon said:
Assuming there are no other variables which reference the delegate,
that's right - and it's just like other reference type when it comes
to garbage collection.
Also, if I am not mistaken, you can only assign a method or another delegate
(which would contain a list of methods - which may only be one).

MyDelegate delegate1 = MethodA; <-- method
MyDelegate delegate2 = delegate1; <-- delegate

Just wanted to be sure on this.

Thanks,

Tom
 
T

tshad

Peter said:
What do you mean by "in either case"? In the event that "the 1st
object will be garbage collected", yes...that's because you can't
access the object. But if the first object cannot be garbage
collected, that implies that something is still referencing it (for
example, a variable that you had copied the reference to), and in
that case you certainly can access the object.

But not directly, only by the reference. The object is actually "invisible"
to you and is only being held in check by the reference.

What I meant was that if you have this:

string a = "test"
string b = a;
a = a + " it"

Now I can access the first object "a" through "b", but I cannot access it
directly by "a" anymore as "a" is the now a new object. If I hadn't
assigned "a" to "b" then when the 2nd object "a" was created, "a" would
disappear.
If those are the two cases you're talking about then I'd have to say
you're incorrect about "in either case". What you wrote is true only
in the case when the object is eligible for garbage collection.

You can't access the object at all, in the 1st case (potentially garbage
collected). In the 2nd case, you can't access it directly. Only via a
reference.

Of course, we could argue that the all strins are accessed by reference.
But my point is that you can't access it by the original name anymore.

Thanks,

Tom
 
T

tshad

Peter said:
Or alternatively (since Tom, your statement implies a possible
misconception about the relationship between the variable and the
delegate instance itself)...

The variable is _not_ a delegate. It's simply a reference to a
delegate. When you "change a delegate", what you really mean is that
you are reassigning a delegate variable. The delegate itself is
immutable; it is literally impossible to "change a delegate".

But yes, when you reassign a delegate variable, it receives the new
reference. Unless you have already assigned the previous reference to
some other variable (and in your sample code, you did), the delegate
instance itself becomes unreachable and can be garbage-collected.

Sorry if this seems like nitpicking, but IMHO it's very very
important to understand and consistently keep in mind the difference
between the object itself, and a variable referencing the object.

I agree and that was what I was trying to get.

Thanks,

Tom
 
J

Jon Skeet [C# MVP]

Also, if I am not mistaken, you can only assign a method or another delegate
(which would contain a list of methods - which may only be one).

Or you can use an anonymous method or a lambda expression, or call a
method which returns a delegate, etc. As Peter says, anything which
can be converted to MyDelegate can be used.
MyDelegate delegate1 = MethodA;     <-- method
MyDelegate delegate2 = delegate1;     <-- delegate

Just wanted to be sure on this.

There's a difference between these two - the first is creating a *new*
instance of MyDelegate. The second is just copying a reference from
one variable to another.

Jon
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top