tags:

views:

182

answers:

4

I have a short events example from .NET 2.0 that I've been using as a reference point for a while. We're now upgrading to 3.5, though, and I'm not clear on the most idiomatic way to do things. How would this simple events example get updated to reflect idioms that are now available in .NET 3.5?

// Args class.
public class TickArgs : EventArgs {
    private DateTime TimeNow;
    public DateTime Time {
        set { TimeNow = value; }
        get { return this.TimeNow; }
    }
}

// Producer class that generates events.
public class Metronome {
    public event TickHandler Tick;
    public delegate void TickHandler(Metronome m, TickArgs e);
    public void Start() {
        while (true) {
            System.Threading.Thread.Sleep(3000);
            if (Tick != null) {
                TickArgs t = new TickArgs();
                t.Time = DateTime.Now;
                Tick(this, t);
            }
        }
    }
}

// Consumer class that listens for events.
public class Listener {
    public void Subscribe(Metronome m) {
        m.Tick += new Metronome.TickHandler(HeardIt);
    }
    private void HeardIt(Metronome m, TickArgs e) {
        System.Console.WriteLine("HEARD IT AT {0}",e.Time);
    }
}

// Example.
public class Test {
    static void Main() {
        Metronome m = new Metronome();
        Listener l = new Listener();
        l.Subscribe(m);
        m.Start();
    }
}
A: 

For starters you can use auto generated properties:

public class TickArgs : EventArgs {
     public DateTime Time {
        set;
        get;
    }
}

You also do not need to instantiate the listening delegate:

public void Subscribe(Metronome m) {
    m.Tick += HeardIt;
}
m0sa
+1  A: 

For a start, you shouldn't define your own delegate types for events. Instead, use System.EventHandler<T> or System.EventHandler.

Also, you can use auto-implemented properties for your TickArgs class (which, incidentally, should really be called TickEventArgs, in accord with .NET conventions):

public class TickEventArgs : EventArgs
{
    public DateTime Time
    {
        get;
        set;
    }
}

As for the events themselves, there are some gotchas that you should know about in .NET which you can read about in some of John Skeet's multithreading articles:

http://www.yoda.arachsys.com/csharp/events.html

Note that events in .NET 4 work differently and a lot of the gotchas that exist in 3.5 have been cleaned up.

Will Vousden
+1  A: 
// Args class.
public class TickArgs : EventArgs {
    private DateTime TimeNow;
    // Auto property used
    public DateTime Time { get; set; }
}

// Producer class that generates events.
public class Metronome {
    public event TickHandler Tick;
    public delegate void TickHandler(Metronome m, TickArgs e);
    public void Start() {
        while (true) {
            System.Threading.Thread.Sleep(3000);
            // Thread safety introduced
            TickHandler ticker = Tick;
            if (ticker != null) {
                // Object initialiser added
                TickArgs t = new TickArgs { 
                   Time = DateTime.Now;
                }
                ticker(this, t);
            }
        }
    }
}

// Consumer class that listens for events.
public class Listener {
    public void Subscribe(Metronome m) {
        // Event handler replaced with llambda function
        m.Tick += (mm, e) => System.Console.WriteLine("HEARD IT AT {0}",e.Time)
    }
}

// Example.
public class Test {
    static void Main() {
        Metronome m = new Metronome();
        Listener l = new Listener();
        l.Subscribe(m);
        m.Start();
    }
}

You can improve the Tick event like this

  // Producer class that generates events.
    public class Metronome {
        // Add a dummy event handler and ensure that there's no unsafe thread issues 
        public event TickHandler Tick = (m, e) => {};
        public delegate void TickHandler(Metronome m, TickArgs e);
        public void Start() {
            while (true) {
                System.Threading.Thread.Sleep(3000);
                // no need to check for null before calling
                Tick(this, new TickArgs { Time = DateTime.Now; });
            }
        }
    }

I don't have a compile to hand but you can improve the Tick event like this I think

  // Producer class that generates events.
    public class Metronome {
        // Add a dummy event handler and ensure that there's no unsafe thread issues 
        public event EventHandler<TickArgs> Tick = (m, e) => {};
        public void Start() {
            while (true) {
                System.Threading.Thread.Sleep(3000);
                // no need to check for null before calling
                Tick(this, new TickArgs { Time = DateTime.Now; });
            }
        }
    }
Preet Sangha
I think pointing out what you've actually changed would help.
Simon P Stevens
added comments (sorry)
Preet Sangha
A: 

Here's how I would do it. First, note that I've made TickArgs sealed and immutable via readonly on the member variables. Second, I removed the TickHandler delegate and replaced with an EventHandler<TickArgs>. Thirdly, the Tick event itself is now private (renamed to _Tick) and accessed via a property. Lastly, the hook into the event in Listener uses a lambda expression (i.e. inline delegate) rather than an explicit method.

namespace Events3
{
    using System;

    // Args class.
    public sealed class TickArgs : EventArgs
    {
        private readonly DateTime TimeNow;
        public DateTime Time
        {
            get { return this.TimeNow; }
        }
        public TickArgs(DateTime TimeNow)
        {
            this.TimeNow = TimeNow;
        }
    }

    // Producer class that generates events.
    public sealed class Metronome
    {
        private event EventHandler<TickArgs> _Tick;
        public event EventHandler<TickArgs> Tick
        {
            add { this._Tick += value; }
            remove { this._Tick -= value; }
        }
        public void Start()
        {
            while (true)
            {
                System.Threading.Thread.Sleep(3000);
                EventHandler<TickArgs> tick = this._Tick;
                if (tick != null)
                {
                    tick(this, new TickArgs(DateTime.Now));
                }
            }
        }
    }

    // Consumer class that listens for events.
    public sealed class Listener
    {
        public void Subscribe(Metronome m)
        {
            m.Tick += (sender, e) =>
            {
                System.Console.WriteLine("HEARD IT AT {0}", e.Time);
            };
        }
    }

    // Example.
    public static class Test
    {
        static void Main()
        {
            Metronome m = new Metronome();
            Listener l = new Listener();
            l.Subscribe(m);
            m.Start();
        }
    }
}
Jesse C. Slicer