[.net] C# - Events vs Traditional Callback Methods

Started by
9 comments, last by ChristianPena 18 years, 4 months ago
Hi, I am working on a messaging system to centralize all text ouput in my game. The scheme that I have will likely seem very traditional: I have a central OutputManager. All objects that want to display output have a reference to that object. They add messages to the OutputManager. Once per frame, the OutputManager dispatches all messages to OutputListeners. When I was doing this in C++, I managed a list of OutputListeners through which I iterated and dispatched the appropriate events. The messages are dispatched on one or more of the 3 available channels: Public, Log, Debug. The OutputListeners subscribe to channels and the OutputManager ensures that the listeners only receive the messages they subscribed to. In C#, I was considering having the OutputManager have 3 public events to raise for each message type. OutputListeners would be registered by having their AddOutput method be a handler for each of the events to which they subscribe. So if a message is on the queue for Public, when it is dispatched, the OutputManager simply invokes the PublicOutputDispatched(string message) event which is then passed on to the interested OutputListeners. I could also just do what I was doing in C++ and store a list of OutputListeners. I haven't perfected the scheme yet, and I would like to have this be more flexible, but for now, I would certainly accept it. I would like to get some feedback on this, however. Are there any performance implications. Is this bad design? Is this what others are doing? Thanks!
Advertisement
I think the C# term your looking for is delegates.

Quote:Original post by blackdot
I could also just do what I was doing in C++ and store a list of OutputListeners.

Which is exactly what a .NET event does for you.

--AnkhSVN - A Visual Studio .NET Addin for the Subversion version control system.[Project site] [IRC channel] [Blog]
Arild:

That is how I have it implemented in C# at the moment. I just wanted to know if this was a good way of doing it.

It is less clean than the C++ approach since I could create channels on the fly without having to change the object definitions; I would just create a Listener that listens on channel 16 and in my program, I could just dispatch to channel 16. In the new approach, since they are events, I will have to create a new event for each channel. But then, I haven't been working on this more than a day, so I haven't looked at it with an eye for reusability.

smr:

If I used non-event delegates, then I would have to store a list of those delegates and iterate through and execute them, correct?
I don't realy understand what you want to do.

Do you want something like:

OutputManager.AddListener( channel_id, some_function );

OutputManager.Write( channel_id, message );

?

But then I don't understand what you mean with non-event delegates... event delegates behave almost equal to non-event delegates. In fact I only know of two.
1. You can't use non-event delegates in interfaces
2. When used on a property you can only add and remove delegates (what you can with simple delegates, too) but you can't use the = operator to set the value.
Quote:Original post by blackdot
smr:

If I used non-event delegates, then I would have to store a list of those delegates and iterate through and execute them, correct?


I think so. Why wouldn't you want to use event delegates? You can make them private or protected to prevent other objects from invoking them if you wish.

No, normal delegates will multicast:
using System;using System.Collections.Generic;using System.Text;public class foo {    public delegate void foodel();    public foodel bar;}public class echoer {    public readonly string s;    public void echo() {        Console.WriteLine(s);    }    public echoer(string ss) { s = ss; }}class Program {    static void Main(string[] args) {        foo         f1 = new foo();        echoer e1 = new echoer("moo.");        echoer e2 = new echoer("bleat.");        f1.bar += e1.echo;        f1.bar += e2.echo;        f1.bar += e1.echo;        f1.bar();    }}


produces the expected:
moo.bleat.moo.


Specifying event is just a quick trick to hide everything in the delegate from other classes but += and -=.

I'm in Visual Studio .NET 2003. The compiler complains on the lines where you add the handlers. I had to change the program to the following to make it work:

class Program {	static void Main2(string[] args) 	{		foo         f1 = new foo();		echoer e1 = new echoer("moo.");		echoer e2 = new echoer("bleat.");		f1.bar += new foo.foodel(e1.echo);		f1.bar += new foo.foodel(e2.echo);		f1.bar += new foo.foodel(e1.echo);		f1.bar();	}}


Thanks for the heads up. I mocked up a class to do this with delegates and it works just fine. As a design decision is this method OK?
Ah yes, I should've prefixed, as I'm using 2k5.

As far as design goes, I've little opinion on the matter. I've been using C# for about a week :]

Personally, I've always used Output objects as sinks, not as dispatchers. C#/.NET makes this easier by providing a better generalized Stream object/readers/writers and the ability to use Synchronized [on some?] to make them threadsafe. Things like logging strategy objects might use events/delegates to dispatch to various sinks, but those aren't [imo] best used through a single manager.
EDIT: I just saw someone gave a similar answer... [grin]. I'll leave mine in here as example.

Actually, you can still use your channel idea, and benefit from the C# delegates/events.

Why not make a Channel class, and have that one dispatch the events?

public void delegate TextOutputEventHandler(object sender, TextOutputEventArgs e);public class Channel{    private int channel;    public event TextOutputEventHandler TextOutput;        public Channel(int channel)    {        this.channel = channel;    }    public int Channel    {        get { return channel; }    }    public void OutputText(string text)    {        if (TextOutput != null)            TextOutput(this, new TextOutputEventArgs(text));    }}public class ChannelManager // Or whatever you want{    // ... Lotsa code here    public void AddOutputHandler(int channel, TextOutputEventHandler handler)    {        channel = channels.GetByKey(channel); // Psuedocode        if (channel != null)        {             channel.TextOutput += handler; // Subscribe        }    }}


This will allow you to use the best of both worlds.

Toolmaker

This topic is closed to new replies.

Advertisement