Loose coupling with Intra-Class events

Posted Sat, Mar 4 2006 0:27 by bill
 
In Visual Studio 2005, we typically have a lot of generated code per application. Strongly typed datasets are a classic example of this. One difficulty of having generated code parts of a class alongside manually written parts has been removed due to the use of partial classes and physical separation of these parts into different files.  The challenge now lays in how do you have these two parts interoperate with each other. How does code in the generated part call into code in the human written part ?  VB solves this with the use of intra-class event coupling.
 
 
The problem :
 
Let's take an example of a Customer class with an Name property As String, and this code is generated for you.  Now assume you want to intercept the property Set and do some complex validation of the value.  
 
'generated code
Partial Class Customer

   Private m_Name As String

   Public Property Name() As String
      Get
         Return m_Name
      End Get
      Set(ByVal value As String)
         'TODO: notify any custom code
         m_Name = value
      End Set
   End Property

End Class
 
' human written code
Partial Class Customer
 
   'TODO add code to intercept the Name being set
 
End Class
 
 
Solutions to the problem :
 
Some approaches taken to this kind of issue is to always provide stubs and use attributes etc to mark those stubs.  this removes the separation of generated code by file, rather the generated code is interweaved with your own code and placeholders are inserted for you to work with. This approach works well except that clean separation is lost, and it requires more work on the code generation tools to parse your code.  Another approach that can be taken is to have conditionally compiled code that checks to see if a function exists and compiled accordingly.  And yet another approach that can be taken is to use inheritance and overridable methods.
 
The inheritance approach falls down very quickly when you have complex object models and factory methods. Admittedly this could be less fragile today due to the use of generics, but still it brings with it an un-due amount of complexity. Conditionally compiled code on the other hand falls down around inheritance, especially when we have separation of code into different assemblies. So of those three approaches I've mentioned, today in Visual studio, the method stubs approach is the preferable one.  But there is one more to consider …
 
The approach you can take in VB is to use intra class events. I'll drill into how these actually work under the covers in a minute, but for now it's easiest to picture them as method stubs that are linked by the compiler. So these in fact work basically the same as using the method stub/placeholder approach, but also provide clean separation of generated code and user code on a per file basis.
 
And the nice thing is in VB they work today, straight out of the box in Visual studio 2005. 
 
 
How intra class events work :
 
Intra class events are the same as any event in VB. The "magic" part is how the compiler injects code to wire these up. Via declarative programming, the VB compiler will inject the code necessary to wire up the generated code and the human written code.  You can see this today in the Strongly Typed Datasets. In VB you can handle the CustomerRowChanging event inside the partial class for the CustomerTable. (note: today in C# you cannot)
 
Let's expand the example we had from above:
 
'generated code
Partial Class Customer
' add an event for the name changing.
' The ValidationEventHandler is just an example of the type of event we could use here
' It passes in old value, new value, property name, and a Cancel argument.
' the NewValue and OldValue are as type T, which in this case is String.
   Private Event NameChanging As ValidationEventHandler(Of String)
  
   Private m_Name As String
 
   Public Property Name() As String
      Get
         Return m_Name
      End Get
      Set(ByVal value As String)
          Dim evArgs as New ValidationEventArgs("Name", Me.m_Name, value, False)
          RaiseEvent NameChanging( Me, evArgs)
          If not evArgs.Cancel Then
             m_Name = evArgs.Value
          End If
       End Set
   End Property

End Class
 
' human written code
Partial Class Customer
 
  
   Public Sub Me_NameChanging(sender As Object, ev As ValidationEventArgs) Handles Me.NameChanging
       'TODO validate the values via the ev parameter's NewValue and OldValue properties
       '         and set ev.Cancel = True to cancel the property set.
   End sub
    
End Class

 
The above example is a simplified use of  events to validate the setting of the Name property. How this actually works is the VB compiler recognizes the Handles declarative syntax and wires the event handler into the object constructors  If you wanted to try to achieve the same in C# you could manually wire up the event handlers in your own constructor, however that approach requires that the generated code does not have any constructors, which is why you can't really do this in c# with strongly typed datasets as the generated code there does provide constructors and needs to.  With VB however, because the declarative syntax tells the compiler to do this wiring, the compiler gets to act as the glue between the generated code and your custom code.
 
The cost
 
The old saying there's no such thing as a free lunch tends to hold true here. the cost however is very minimal.  You may for example want the NameChanging event to be public in your object model, so really there is not overhead.  In other cases, the cost basically is an IntPtr slot for each declared Event, although you can minimize that via collection for all the events. should you actually choose to wire up the event then that IntPtr will point to a multicast delegate which will store a function pointer and a pointer to self. The invocation is also minimal overhead, as it is basically calling delegates in a list. So memory or preformance issues are pretty insignificant, and can actually be favorable compared to inheritance or method stub approaches.
 
One concern you might "one day" need to be aware of is that event invocation does not guarantee the order the listeners will be invoked in the cases where there are more than one listener to the same event.  That being said, the behavior is FIFO (first in first out), and as your intra class events are wired in at the start of the constructor, they are always first in, hence are today first to be notified.
Should that ever change, or should you require further control on the order of the events invocation, you can in VB.NET use the Custom Event syntax which let's you write the Raise method as well as modify the Add and Remove methods,
 
   Public Custom Event SomeEvent As EventHandler
      AddHandler(ByVal value As EventHandler)
          'TODO
      End AddHandler

      RemoveHandler(ByVal value As EventHandler)
          'TODO 
      End RemoveHandler

      RaiseEvent(ByVal sender As Object, ByVal e As System.EventArgs)
          ' ensure events are raised the way you want them to be in here
      End RaiseEvent
   End Event
The VB syntax shown here is as per the MSIL specification, and corresponds to the MSIL of .addon, .removeon, and .fire. 
In C# however they only support the .addon and .remove on, so you have to write a separate method to try to simulate the .fire. Typically the OnXXX pattern is used for this, although great care and code analysis really needs to be run to ensure all event invocation then goes via the OnXXX method.  The .fire method provides an nicer encapsulation of this.
 
Conclusion
 
Hopefully you can now see, or will after a bit of experimentation with the above concepts, that intra class events provide a flexible and powerful way of wiring up generated code with manually written code.  And here once again we have VB leading the way with modern concepts such as declarative programming, and also with full specification implementation of things such as custom Events.  Clearly C# has much catching up to do in these areas. 
 
So next time you start to build your business object layers, or any code that may center around code generation, then think about your choice of language.  It obviously makes a lot of sense to use VB for these kinds of applications as only VB today provides such great coupling via intra class event handling.
 
 
Filed under: , ,

Comments

# VB Intra-Class Events

Sunday, March 05, 2006 3:53 AM by Corrado's BLogs

# re: Loose coupling with Intra-Class events

Sunday, April 30, 2006 3:56 AM by Maurice

Hi Bill,

Although a typed DataTable had a generated constructor this also includes a call to a virtual BeginInit() and EndInit() function. Using the EndInit() function you can hook up the CustomerRowChanging event handler in C#.

Maurice