User:Henon
From eqqon
Henon's Blog
- Other articles by Henon
- Preventing Recursive Events in C# (21 November 2007)
- Visitor Pattern (21 November 2007)
- Navigating in Exception Stack Traces in Visual C# (9 November 2007)
- Ruby-like instance variable syntax in C# (30 October 2007)
- Streaming between Threads or Processes (30 October 2007)
- The Future of Ruby (6 August 2007)
- Links
Preventing Recursive Events in C#
Events that cause each other to be fired recursively are usually a problem when two classes need to sync data. In large programs these recursive call chains can be quite long and thus a recursion might not be expected when firing some event. The recursion ends in a StackOverflowException. There are many possibilities to resolve such a problem. The easiest one is to introduce a boolean locking field which prevents reentrancy:
if(MyEvent != null)
{
m_MyEvent_lock=true
MyEvent();
m_MyEvent_lock=false
}
This is fine unless you need to lock a lot of events and the locking fields are spamming your classes. Another way to cope with recursive events is the Mediator Pattern by [Gamma et al 95]. If there are a number of objects which need to keep data in sync they don't notify each other via events but notify a common mediator. The mediator then notifies the others and makes sure not to notify the object that has notified him. It is often the best with technical problems to rethink the design (i.e. apply the Mediator Pattern) but sometimes we just want an elegant technical solution.
EventLocker
An elegant technical solution to the recursive event problem is my EventLocker. It keeps a Hashtable of currently invoked (=locked) delegates and stops recursive invocations. The EventLocker keeps your classes clean of locking noise. However, you should only use it for potentially recursive event invocations because dynamically invoking a delegate is inherently slower than normal typesafe event invocation.
public class EventLocker
{
static Hashtable m_lock;
static EventLocker()
{
m_lock = new Hashtable();
}
public static void Invoke(Delegate d, params object[] args)
{
if (d != null && !m_lock.ContainsKey(d))
{
m_lock.Add(d, true);
d.DynamicInvoke(args);
m_lock.Remove(d);
}
}
public static void InvokeThreadsafe(Delegate d, params object[] args)
{
lock (m_lock) Invoke(d, args);
}
}
--Henon 23:45, 21 November 2007 (CET)
Motivation for applying the Visitor Pattern (C#)
Imagine you have a big class hierarchy and you are writing a sophisticated class that does something different for each type in the hierarchy. You cannot put the functionality into the respective classes for some reason. Here is a very simplified code example which demonstrates the situation and implements a naive solution.
Example
public class A {}
public class B : A {}
public class C : B {}
public class Printer
{
public void Print(A element) { Console.WriteLine("A"); }
public void Print(B element) { Console.WriteLine("B"); }
public void Print(C element) { Console.WriteLine("C"); }
public void Print(A[] array)
{
foreach (A element in array)
{
// here we reimplement polymorphic method dispatching!
if (element is C)
Print(element as C);
else if (element is B)
Print(element as B);
else if (element is A)
Print(element);
}
}
}
Applying the Visitor Pattern
As you can imagine this naive implementation would become very complicated for a larger and deeper class hierarchies. It would be error prone and unmaintainable. Every time you add a new class to your hierarchy you would need to extend the dispatching logic in the Printer class.
The Visitor Pattern [Gamma et al. 95] offers a better solution. It makes use of the polymorphic method dispatching functionality of object oriented programming languages (also called late binding) effectively eliminating the error prone part of our naive implementation. The idea is to double dispatch the method call by asking each type to re-dispatch the call to Print accordingly. The drawback is that we have to extend our class hierarchy with boilerplate dispatching methods like this:
public class B : A
{
public override void Print(VisitorPattern.Printer printer) { printer.Print(this); } // calls Print(B element)
}
These, however, are not complicated and thus less prone to errors. Of course the Visitor Pattern is prone to another type of error, namely, forgetting the dispatching method for some subtype in the hierarchy. See the following example program for the implementation of the Visitor Pattern.
The complete example program
using System;
using System.Collections.Generic;
using System.Text;
namespace CodeSnippet
{
public class A
{
public virtual void Print(VisitorPattern.Printer printer) { printer.Print(this); } // calls Print(A element)
}
public class B : A
{
public override void Print(VisitorPattern.Printer printer) { printer.Print(this); } // calls Print(B element)
}
public class C : B
{
public override void Print(VisitorPattern.Printer printer) { printer.Print(this); } // calls Print(C element)
}
namespace NaiveApproach
{
public class Printer
{
public void Print(A element) { Console.WriteLine("A"); }
public void Print(B element) { Console.WriteLine("B"); }
public void Print(C element) { Console.WriteLine("C"); }
public void Print(A[] array)
{
foreach (A element in array)
{
// here we reimplement polymorphic method dispatching!
if (element is C)
Print(element as C);
else if (element is B)
Print(element as B);
else if (element is A)
Print(element);
}
}
}
}
namespace VisitorPattern
{
public class Printer
{
public void Print(A element) { Console.WriteLine("A"); }
public void Print(B element) { Console.WriteLine("B"); }
public void Print(C element) { Console.WriteLine("C"); }
public void Print(A[] array)
{
foreach (A element in array)
{
// here we make use of the language's polymorphic method dispatching
element.Print(this);
}
}
}
}
class Program
{
static void Main(string[] args)
{
A[] array = { new A(), new B(), new C() };
Console.WriteLine("Naive Approach:");
new NaiveApproach.Printer().Print(array);
Console.WriteLine("Visitor Pattern:");
new VisitorPattern.Printer().Print(array);
Console.ReadLine();
}
}
}
--Henon 11:16, 21 November 2007 (CET)