views:

2632

answers:

4

Hi

I recently started digging into C# but I can't by my life figure out how delegates work when implementing the observer/observable pattern in the language.

Could someone give me a super-simple example of how it is done? I have googled this, but all of the examples I found were either too problem-specific or too "bloated".

+18  A: 

The observer pattern is usually implemented with events.

Here's an example:

using System;

class Observable
{
    public event EventHandler SomethingHappened;

    public void DoSomething()
    {
        EventHandler handler = SomethingHappened;
        if (handler != null)
        {
            handler(this, EventArgs.Empty);
        }
    }
}

class Observer
{
    public void HandleEvent(object sender, EventArgs args)
    {
        Console.WriteLine("Something happened to " + sender);
    }
}

class Test
{
    static void Main()
    {
        Observable observable = new Observable();
        Observer observer = new Observer();
        observable.SomethingHappened += observer.HandleEvent;

        observable.DoSomething();
    }
}

See the linked article for a lot more detail.

Jon Skeet
Nice, clear example Jon
Dan Diplo
To save yourself a few lines and avoid the null check, initialize your event like this: http://stackoverflow.com/questions/340610/create-empty-c-event-handlers-automatically/340618#340618
Dinah
+6  A: 

Here's a simple example:

public class ObservableClass
{
    private Int32 _Value;

    public Int32 Value
    {
        get { return _Value; }
        set
        {
            if (_Value != value)
            {
                _Value = value;
                OnValueChanged();
            }
        }
    }

    public event EventHandler ValueChanged;

    protected void OnValueChanged()
    {
        if (ValueChanged != null)
            ValueChanged(this, EventArgs.Empty);
    }
}

public class ObserverClass
{
    public ObserverClass(ObservableClass observable)
    {
        observable.ValueChanged += TheValueChanged;
    }

    private void TheValueChanged(Object sender, EventArgs e)
    {
        Console.Out.WriteLine("Value changed to " +
            ((ObservableClass)sender).Value);
    }
}

public class Program
{
    public static void Main()
    {
        ObservableClass observable = new ObservableClass();
        ObserverClass observer = new Observer(observable);
        observervable.Value = 10;
    }
}

Note:

  • This violates a rule in that I don't unhook the observer from the observable, this is perhaps good enough for this simple example, but make sure you don't keep observers hanging off of your events like that. A way to handle this would be to make ObserverClass IDisposable, and let the .Dispose method do the opposite of the code in the constructor
  • No error-checking performed, at least a null-check should be done in the constructor of the ObserverClass
Lasse V. Karlsen
+2  A: 

Check this introduction to Rx Framework which uses wonderful IObserver-IObservable non-blocking asyncronous programming model http://themechanicalbride.blogspot.com/2009/07/introducing-rx-linq-to-events.html

Ray
A fine thing if you use Silverlight - unfortunately not of much use for the rest of us. A shame.
Jeremy McGee
You can convert it to .net clr. See here http://evain.net/blog/articles/2009/07/30/rebasing-system-reactive-to-the-net-clr
Ray
Also it will be a part of .NET 4.0 (and not only for Silverlight)
Ray
+3  A: 

I've tied together a couple of the great examples above (thank you as always to Mr. Skeet and Mr. Karlsen) to include a couple of different Observables and utilized an interface to keep track of them in the Observer and allowed the Observer to to "observe" any number of Observables via an internal list:

namespace ObservablePattern
{
    using System;
    using System.Collections.Generic;

    internal static class Program
    {
        private static void Main()
        {
            Observable observable = new Observable();
            AnotherObservable anotherObservable = new AnotherObservable();

            using (Observer observer = new Observer(observable))
            {
                observable.DoSomething();
                observer.Add(anotherObservable);
                anotherObservable.DoSomething();
            }

            Console.ReadLine();
        }
    }

    internal interface IObservable
    {
        event EventHandler SomethingHappened;
    }

    internal class Observable : IObservable
    {
        private event EventHandler somethingHappened;

        public event EventHandler SomethingHappened
        {
            add
            {
                this.somethingHappened += value;
            }

            remove
            {
                this.somethingHappened -= value;
            }
        }

        public void DoSomething()
        {
            EventHandler handler = this.somethingHappened;

            Console.WriteLine("About to do something.");
            if (handler != null)
            {
                handler(this, EventArgs.Empty);
            }
        }
    }

    internal class AnotherObservable : IObservable
    {
        private event EventHandler somethingHappened;

        public event EventHandler SomethingHappened
        {
            add
            {
                this.somethingHappened += value;
            }

            remove
            {
                this.somethingHappened -= value;
            }
        }

        public void DoSomething()
        {
            EventHandler handler = this.somethingHappened;

            Console.WriteLine("About to do something different.");
            if (handler != null)
            {
                handler(this, EventArgs.Empty);
            }
        }
    }

    internal class Observer : IDisposable
    {
        private static readonly object listLocker = new object();
        private static readonly ReaderWriterLockSlim listItemLocker = new ReaderWriterLockSlim();
        private ICollection<IObservable> observables;
        private EventHandler eventHandler;

        public Observer()
        {
        }

        public Observer(IObservable observable)
        {
            if (observable == null)
            {
                return;
            }

            this.observables = new List<IObservable>
            {
                observable
            };

            this.eventHandler = this.HandleEvent;
            observable.SomethingHappened += this.eventHandler;
        }

        public void Add(IObservable observable)
        {
            if (observable == null)
            {
                return;
            }

            if (this.observables == null)
            {
                lock (listLocker)
                {
                    if (this.observables == null)
                    {
                        this.observables = new List<IObservable>();
                        this.eventHandler = this.HandleEvent;
                    }
                }
            }

            listItemLocker.EnterWriteLock();
            try
            {
                this.observables.Add(observable);
                observable.SomethingHappened += this.eventHandler;
            }
            finally
            {
                listItemLocker.ExitWriteLock();
            }
        }

        public void Remove(IObservable observable)
        {
             if ((observable == null) || (this.observables == null))
             {
                 return;
             }

             listItemLocker.EnterWriteLock();
             try
             {
                 this.observables.Remove(observable);
                 observable.SomethingHappened -= this.eventHandler;
             }
             finally
             {
                 listItemLocker.ExitWriteLock();
             }
        }

        #region IDisposable Members

        public void Dispose()
        {
            if (this.observables != null)
            {
                foreach (IObservable observable in this.observables)
                {
                    observable.SomethingHappened -= this.eventHandler;
                }
            }

            GC.SuppressFinalize(this);
        }

        #endregion

        private void HandleEvent(object sender, EventArgs args)
        {
            Console.WriteLine("Something happened to " + sender);
        }
    }
}
Jesse C. Slicer