Generic object visitor pattern

From Catglobe Wiki
Revision as of 08:31, 5 October 2011 by Cg pham (talk | contribs)
Jump to: navigation, search

<accesscontrol>Administrators,,Cem,,Maysunshine</accesscontrol>

New Report Design - 2009 => Generic object visitor pattern

Introduction

Visitor pattern is a common design pattern in programming world. It is usually used for object navigation. For example, in Xml serialization, we need to go through all properties of an object to serialize them into Xml. Properties of object require different treatment base on its type, and if it is an object then possibly the recursion would happen.

Sample implementation could be found in ReportLayout2006's project, class name AspRomVisitor

Inspiration

The pattern is perfect for navigating objects, however there are cases that we cannot touch code of some class to implement the Accept method. More over, if there are hundreds of properties then it is not reasonable to implement the accept method on all classes just to call back to the visitor's method.

So, to overcome such a problem, I came to implementation of a custom visitor that utilize the power of reflection and dynamic delegate.

Design Decisions

  • Visit only instance public properties
  • Object inspection at runtime must be cached

Design Details

ObjectVisitor.gif

The implementation is rather simple, the entry point for the whole design is method Visit of Visitor class. The method receives an object to be visited plus a VisitContext which brings all necessary information for visiting process. However, the main processing is not inside the Visitor object but VisitMethodImpl. This class provide methods for registering and retrieving of visit methods plus method for getting properties that are visitable.

VisitMethodImpl in turns use services from TypeCache for caching serializable properties and DelegateHelper to help it overcomes some reflection limitation.

VisitMethodImpl provides 2 methods to deal with visit method's implementation.

     protected void AddVisitMethod<T>(Action<T, TContext> visit);
     internal Action<object, TContext> GetVisitDelegate(Type type)
  • AddVisitMethod: register a visit method for a specific Type
  • GetVisitDelegate: get a visit method for a specific Type. Visit method's searching process is as below:
  1. Search for exact match of the required Type
  2. Search for matches with parent types => return the first registered parent's Type
  3. If nothing is found and there is a visit method for object type then return this visit method

PropertyInfoWrapper contains the most tricky of the implementation (the detail can be found in section Lession Learn).

How to implement VisitMethodImpl?

See Tabulation Script for setting inline style to Table Chart for a complete implementation of VisitMethodImpl. But merely said, the implementation of VisitMethodImpl MUST:

  • Provide a type that inherits from VisitContext or can use the VisitContext itself
  • Create private method that conform to type Action<T, VisitContext> where T is the accepted type
  • In constructor, register all methods that can be used as visit methods

Example:

internal abstract class StyleApplier : VisitMethodImpl
{
      protected StyleApplier()
      {
         AddVisitMethod<TableBorderLineStyle>(Apply);
         AddVisitMethod<PaddingProperty>(Apply);
         AddVisitMethod<object>(Apply);
      }

      public void Apply(TableBorderLineStyle borderLineStyle, VisitContext context) { }
     
      public void Apply(PaddingProperty padding, VisitContext context) { }
    
      public void Apply(object genericStyle, VisitContext context) { } 
   }
}

Lesson Learn

The biggest lesson learn that must be shared is using of Reflection against generic method.

Collection of delegate

The generic method syntax is very nice in term of strongly typed and is best for end-developer. For example: it is better to write code as

    public void Apply(PaddingProperty padding, VisitContext context)
    {
         ...
    }

instead of

    public void Apply(object padding, VisitContext context)
    {
         PaddingProperty p = padding as PaddingProperty;
         ...
    }

However, we cannot store a generic method of different type to the same collection, for example: Action<int, VisitContext> and Action<string, Context> are considered different and can not be stored in same collection. With dynamic implementation we need to store all visit method in one collection in order to be capable of searching. And the generic type for all visit method must be Action<object, VisitContext>. The following code show the dictionary of visit methods and how it can be used.

   public class VisitMethodImpl<TContext> where TContext : VisitContext
   {
      private readonly IDictionary<Type, Action<object, TContext>> _visitMethodMapping =
         new Dictionary<Type, Action<object, TContext>>();

      protected void AddVisitMethod<T>(Action<T, TContext> visit)
      {
         if (visit == null) throw new ArgumentNullException("visit");

         // Convert to generic visit with target visit object as {object}
         Action<object, TContext> genericMethod = (o, c) => visit((T)o, c);
      }
   }

Line 11 contains a conversion from Action<T, TContext> to Action<object, TContext> for storing into the collection. Please distinguish between the declaration aspect and runtime aspect of the statement. So, there are actually 2 delegates

  1. Action<T, TContext> => this is the REAL implementation provided by end-developers.
  2. Action<object, TContext> => this act as a place holder for the above delegate in the dictionary, it receives an instance of type T as parameter, but in form of a non-typed object. Thus, the instance must be cast into type T and pass to the REAL method.

PropertyInfoWrapper

Dynamically inspecting of object requires some kinds of reflection. For example, to get/set value from a property we need a PropertyInfo. From that PropertyInfo we can then access GetGetMethod/GetSetMethod to get/set value from/to the property. As the nature of reflection, it requires some overhead calling method dynamically thus decrease the performance a bit. In order to overcome the issue, the model depicted at this link has been followed.

The code of PropertyInfoWrapper is rather simple

   public class PropertyInfoWrapper
   {
      public PropertyInfo PropertyInfo { get; private set; }
      public Action<object, object> SetValue { get; private set; }
      public Func<object, object> GetValue { get; private set; }

      public PropertyInfoWrapper(PropertyInfo p)
         : this(p, p.CreateSetValueDelegate(), p.CreateGetValueDelegate())
      {
      }

      public PropertyInfoWrapper(PropertyInfo p, Action<object, object> setValue, Func<object, object> getValue)
      {
         Debug.Assert(p != null);
         PropertyInfo = p;
         SetValue = setValue ?? ((o, v) => { }); // Do nothing if there is no set method; 
         GetValue = getValue ?? (o => null); // Return null if there is no get method; 
      }
   }
  • The class contains an internal PropertyInfo which point to the real property, and 2 delegates for getting/setting value from/to that property
  • There are 2 constructors, the second one allow developers provide custom implementation of get/set method. If no custom implementation is required, then the 2 delegates is created using 2 methods fetched from GetGetMethod and GetSetMethod using above mentioned link.

Document revisions

Version No. Date Changed By Description Svn revision
0.1 21.09.2009 Nguyen Trung Chinh First version 56374