Showing posts with label design patterns. Show all posts
Showing posts with label design patterns. Show all posts

18 July 2010

Creating a light-weight visitor, fluently in C#

In object-oriented programming a common problem is performing some conditional logic based on the type of an object at run-time. For example, one form you may come across is:

public void DoStuff(MemberInfo memberInfo)
{
 EventInfo eventInfo = memberInfo as EventInfo;
 if(eventInfo != null)
 {
  // do something
  return;
 }

 MethodInfo methodInfo = memberInfo as MethodInfo;
 if(methodInfo != null)
 {
  // do something
  return;
 }

 PropertyInfo propertyInfo = memberInfo as PropertyInfo;
 if(propertyInfo != null)
 {
  // do something
  return;
 }

 throw new Exception("Not supported.");
}

Drawbacks to this being you have to wrap the whole thing in a method to make use of the "bomb out" return statements and it's quite a lot of code repetition which, as I’ve talked about previously, I'm not a fan of. Another example is a dictionary type->operation lookup:

// set up some type to operation mappings
static readonly Dictionary<Type, Action<MemberInfo>> operations = new Dictionary<Type, Action<MemberInfo>>();

// probably inside the static constructor...
operations.Add(typeof(EventInfo), memberInfo => 
{
 EventInfo eventInfo = (EventInfo)memberInfo;
 // do somthing 
});
operations.Add(typeof(MethodInfo), memberInfo =>
{
 MethodInfo methodInfo = (MethodInfo)memberInfo;
 // do something
});
operations.Add(typeof(PropertyInfo), memberInfo =>
{
 PropertyInfo propertyInfo = (PropertyInfo)memberInfo;
 // do something
});

// use it like this...
Type type = memberInfo.GetType();
Type matchingType = operations.Keys.FirstOrDefault(t => t.IsAssignableFrom(type));
if(matchingType != null)
{
 operations[matchingType](memberInfo);
}

The major drawback with this method is that you have to use IsAssignableFrom otherwise it doesn't match inherited types. In fact, the above example doesn't work if you just look up the type of memberInfo directly because we'll get types derived from EventInfo etc, not those types themselves. We also still need to cast to the type we want to work with ourselves and enumerating the dictionary isn’t ideal from a performance point of view.

The GoF pattern for solving this is the visitor which I’ve blogged about in the past however this is rather heavy duty, especially if your "do something" is only one line. It is much more performant than the alternatives though, as it’s using low level logic inside the run-time to make the decision about which method to call, so that should be a consideration.

Then next best alternative to the proper visitor is the first ...as...if...return... form but we can wrap it up quite nicely with a couple of extension methods to cut down on the amount of code we have to write. Here’s a trivial example trying to retrieve the parameters for either a method or a property. Depending on the type we need to call a different method so we identify that method using a fluent visitor:

private Type[] GetParamTypes(MemberInfo memberInfo)
{
 Func<ParameterInfo[]> paramGetter = null;

 memberInfo
  .As<MethodInfo>(method => paramGetter = method.GetParameters)
  .As<PropertyInfo>(property => paramGetter = property.GetIndexParameters)
  .As<Object>(o => { throw new Exception("Unsupported member type."); });

 return paramGetter().Select(pi => pi.ParameterType).ToArray();
}

The As extension attempts to cast “this” as the type specified by the type parameter T and if successful calls the supplied delegate. The overload used in the example above will skip the remaining As calls once one has been successful. There is a second overload which takes a Func<T, bool> rather than an Action<T> and will continue to try the next As if false is returned from the Func. The last As call, by specifying Object as the type, is a catch all and allows providing a default implementation or catering for an error case as shown above. The extensions are implemented like so:

/// <summary>
/// Tries to cast an object as type <typeparamref name="T"/> and if successful 
/// calls <paramref name="operation"/>, passing it in.
/// </summary>
/// <typeparam name="T">Type to attempt to cast <paramref name="o"/> as</typeparam>
/// <param name="o"></param>
/// <param name="operation">Operation to be performed if cast is successful</param>
/// <returns>
/// Null if the object cast was successful, 
/// otherwise returns the object for chaining purposes.
/// </returns>
public static object As<T>(this object o, Action<T> operation)
 where T : class
{
 return o.As<T>(obj => { operation(obj); return true; });
}

/// <summary>
/// Tries to cast an object as type <typeparamref name="T"/> and if successful 
/// calls <paramref name="operation"/>, passing it in.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="o"></param>
/// <param name="operation">Operation to be performed if cast is successful, must return 
/// a boolean indicating whether the object was handled.</param>
/// <returns>
/// Null if the object cast was successful and <paramref name="operation"/> returned true, 
/// otherwise returns the object for chaining purposes.
/// </returns>
public static object As<T>(this object o, Func<T, bool> operation)
 where T : class
{
 if (Object.ReferenceEquals(o, null)) return null;

 T t = o as T;
 if (!Object.ReferenceEquals(t, null))
 {
  if (operation(t)) return null;
 }
 return o;
}

16 May 2008

Implicit polymorphism and lazy collections in NHibernate

If you create a lazy loaded property or collection in NHibernate which can contain any type from a class hierarchy, for example by having mappings like this:

<?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">
  <class name="Mammal" table="Mammal">
    <discriminator column="mammal_type" type="String"/>
    ...
    <subclass name="Cat" discriminator-value="CAT">
      ...
      <subclass name="DomesticCat" discriminator-value="CAT-DOMESTIC">
        ...
      </subclass>
    </subclass>
    <subclass name="Dog" discriminator-value="DOG">
      ...
    </subclass>
  </class>
  <class name="Zoo" table="Zoo">
    ...
    <bag name="Animals">
      <key column="zoo_fkey"/>
      <one-to-many class="Mammal"/>
    </bag>
  </class>
</hibernate-mapping>

You'll find that on requesting your objects from your collection they will be of a special new type NHibernate has created, derived from your base class which in this case is "Mammal".

This is a real problem because it means that you can't perform is or as operations on it to determine which actual type it is and you can't cast it to access properties and methods of your derived types.

The solution is to use the Visitor pattern which is described in detail on this site with a couple of examples in C# on this site. Essentially it involves creating a class with a method which is overloaded for each of the types in your class hierarchy.

class MammalVisitor
{
  public void Visit(Cat c) { ... Cat operations ... }
  public void Visit(DomesticCat dc) { ... DomesticCat operations ... }
  public void Visit(Dog d) { ... Dog operations ... }
}

This "visitor" object is then passed to a method defined on the base class of your hierarchy and then subsequently overridden on each derived type.

class Mammal
{
  public virtual void Accept(MammalVisitor mv) { mv.Visit(this); }
  ...
}

class Cat : Mammal
{
  public override void Accept(MammalVisitor mv) { mv.Visit(this); }
  ...  
}

class Dog : Mammal
{
  public override void Accept(MammalVisitor mv) { mv.Visit(this); }
  ...  
}

These methods simply call the visitor's method passing this to it which in turn will automatically execute the correct overload.

Mammal m; // some unknown derived type of mammal
MammalVisitor mv = new MammalVisitor();
m.Accept(mv);

These overloaded methods can then perform your type specific functions. In the example above you have no knowledge of the type of Mammal that you have however when you call Accept the relevant code is automatically executed. If Mammal happens to be a Cat type then the Cat overloaded Visit method is called.