views:

362

answers:

2

Suppose I want to create a set of observers based on type. That is to say, when they are notified of an event, they are told the type of one of the arguments and then decides whether or not to act based on if it can operate on that type.

Are there any simple ways to do this? I figured this would be fairly simple to do with generics, but that seems to be turning out to be harder than I imagined. And I would prefer not to have to deal with casting a bunch of references to object if I can avoid it.

Where I'm getting stuck is in doing this:

public delegate void NotifyDelegate<T>(IEnumerator<T> loadable, NotifyArgs na);

interface IObserver
{
    void Notify<T>(IEnumerator<T> loadable, NotifyArgs na);
}

class Subject
{
    NotifyDelegate notifier;  //won't compile:  needs type args

    void Register(IObserver o)
    {
        notifier += o.Notify;
    }
}

Of course, I could make the Subject generic as well, but then I have to have a separate Subject for each type. Does anyone have any advice here? Is there some piece of functionality that I'm missing somewhere or am I overcomplicating this?

UPDATE: I did oversimplify the arguments that Notify and NotifyDelegate take. Instead of this:

public delegate void NotifyDelegate<T>(NotifyArgs na);

I'm actually wanting to do something like this:

public delegate void NotifyDelegate<T>(IEnumerator<T> loadable, NotifyArgs na);

What I'm basically trying to pass back and forth is data from a database. Sorry if the previous code sample confused anyone.

+2  A: 

First off, change the code you've got to the following:

interface IObserver
{
}

class Subject
{
  public Subject ()
  {
    m_observers = new List<IObserver> ();
  }

  public void Register (IObserver o)
  {
    m_observers.Add (o);
  }

  List<IObserver>
    m_observers;
}

Then, use reflection to find an appropriate function based on parameter type:

  public void NotifyObservers (object param)
  {
    foreach (IObserver observer in m_observers)
    {
      foreach (MethodInfo method in observer.GetType ().GetMethods (BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy | BindingFlags.Instance))
      {
        if (method.Name == "Notify")
        {
          ParameterInfo []
            parameters = method.GetParameters ();

          if (parameters.Length == 1 && parameters [0].ParameterType == param.GetType ())
          {
            method.Invoke (observer, new object [] { param });
            break;
          }
        }
      }
    }
  }

and use it like this:

class Observer : IObserver
{
  public Observer (Subject s)
  {
    s.Register (this);
  }

  void Notify (float value)
  {
    System.Diagnostics.Trace.WriteLine ("float value = " + value);
  }

  void Notify (int value)
  {
    System.Diagnostics.Trace.WriteLine ("int value = " + value);
  }
}

static void Main (string [] args)
{
  Subject
    s = new Subject ();

  Observer
    o = new Observer (s);

  float
    v1 = 3.14f;

  int
    v2 = 42;

  System.Diagnostics.Trace.WriteLine ("sending float");
  s.NotifyObservers (v1);
  System.Diagnostics.Trace.WriteLine ("sending int");
  s.NotifyObservers (v2);
}

Skizz

Skizz
Interesting idea. It's a bit more complicated than I want to implement, but at least it looks like it'll work if I can't figure out a simpler way.
Jason Baker
+1  A: 
interface IObserver
{
    void Notify(NotifyArgs na);
    bool SupportsType(Type t);
}

class Subject
{
    List<IObserver> observers;

    void Register(IObserver o)
    { observers.Add(o);
    }

   void OnNotify(Type t, NotifyArgs args)
    {
      foreach (IObserver o in observers)  
      {
        if (o.SupportsType(t)) o.Notify(args));
      }
    }
}
Mark Cidade