Login | Register   
LinkedIn
Google+
Twitter
RSS Feed
Download our iPhone app
TODAY'S HEADLINES  |   ARTICLE ARCHIVE  |   FORUMS  |   TIP BANK
Browse DevX
Sign up for e-mail newsletters from DevX


advertisement
 

Build Custom Code Generators in C# : Page 2

You don't have to rely on libraries and frameworks to avoid writing repetitive code; instead, learn to generate such code automatically, using a custom input format to describe the code you want to generate.


advertisement
Planning a Generator
I said that I like to start with the output first, so prepare yourself a bit here. I'm not trying intimidate you, but the long and intricate output in Listing 1 illustrates the reasons why you might want to use code generation to begin with. If you take a close look at the code in Listing 1, you'll see that it's not that complex, but writing such code repeatedly takes a lot of time, and you certainly wouldn't want to do it very many times by hand:

With the sample output in hand, you can start listing items, as described earlier. The main sections of the output fall into these four major operations:

  1. Properties and Fields
  2. The Schema stuff
  3. ReadXml
  4. WriteXml
These four major operations are, you'll find, pretty much the same in every case when generating a DTO that implements IXmlSerialzable. That's good news, because the list describes not only the Address object, but any object that this generator will be able to produce. You want to make sure that the list remains generalizable to all output cases as you develop it. In this case, if the outline becomes Address-specific the generator won't be able to produce anything but addresses.

Still looking at Listing 1 and starting from the top of the file, here are some more recognizable (yet still neutral across the domain) details:

  • File Header Info
  • Opening Comment
  • Using Statements
  • Class Declaration
  • XmlRoot attribute
  • SchemaProviderAttribute
  • Class Body
  • Properties and Fields
  • Helper Methods (ToXml, CreateFromXml)
  • GetProviderSchema (SchemaProvider attribute wires to this)
  • ReadXml
  • Read each property
  • Read atoms
  • Read lists of atoms (arrays)
  • WriteXml
  • Write each property (NOT the root element!)
  • Write atoms
  • Write lists of atoms (arrays)
  • Close the Class ( "}")
  • Close the File header info (Namespace) ( "}")
Now that looks fairly organized, and it completely covers the list of things in the Address example specifically, but you also want to be able to support properties that are entire objects, not just single values or lists of values. In fact, because objects often contain collections of other objects, you must allow for that possibility as well. I like to call sub-objects comcompositesposites, because that's what they are essentially, a composition of either sub-objects, or sub-atoms and lists or arrays of either, so here's the final abstract, with new items in bold text:

  • File Header Info
  • Opening Comment
  • Using Statements
  • Class Declaration
  • XmlRoot attribute
  • SchemaProviderAttribute
  • Class Body
  • Properties and Fields
  • Helper Methods (ToXml, CreateFromXml)
  • GetProviderSchema (SchemaProvider attribute wires to this)
  • ReadXml
  • Read each property
  • Read atoms
  • Read composites (sub-objects)
  • Read lists of atoms (arrays)
  • Read lists of composites (arrays of sub-objects)
  • WriteXml
  • Write each property (NOT the root element!)
  • Write atoms
  • Write composites
  • Write lists of atoms
  • Write lists of composites
  • Close the Class ( "}")
  • Close the File header info (Namespace) ( "}")
Now it's time to create the intermediate input form. When building code generators, you should plan spend the bulk of your time and consideration on designing the intermediate form domain. It's useful to be able to easily store and retrieve this intermediate form to and from disk. .NET makes it easy to store and retrieve XML from disk, so XML makes a good intermediate format. The intermediate structures will be read from and written to XML files.

It's important that the intermediate form be capable of encapsulating the entire domain's set of variations, so consider the general case. You want to generate DTO objects. Basically these objects consist of a set of properties. Each property can vary by category (atom or composite), cardinality (single, or list) and by type. For demo purposes, I'll constrain atoms to only a few types: String, Boolean, Int16, Int32, Int64, DateTime, Float, and Double. Taken together, category and cardinality have only a few possibilities, so we can fuse those together into one variation point. Here's a set of enumerations and small classes to hold intermediate data:

enum DataType { String, Boolean, Int16, Int32, Int64 DateTime, Float, Double } enum PropertyType { Atom, Composite, ListAtoms, ListComposites } class ObjectDefinition { public String TypeName; public String XmlNameSpace; public List<PropertyDefinition> Property; ... } class PropertyDefinition { public String DeclaringTypeName; public String CompositeTypeName; public String PropertyName; public PropertyType PropType; public DataType AtomicType; ... }

Note: The preceding enumerations and classes are trimmed-down versions of the actual code for these. I actually generated the ObjectDefinition, PropertyDefinition, DataType, and PropertyType from an XML Schema Definition file (XSD), which is included in the download, but beyond the scope of the article. The relevant point is that the types are easily serializable to and from XML using the default serializer (it's OK to use the default serializer for simple things).

Because these object definitions are serializable to XML, you can define them using XML files, and let the generator read the definitions from the files. Listing 2 shows the XML for the intermediate form of an Address object:

Listing 2 may look complicated, but it's actually just peanuts compared to what we're about to get. In the XML above, Address is a class with an integer (AtomicType: Int32) property, ID. It has several string properties: Street, City, and State, and an integer (Int32) Zip property. All these properties are singular values, so the intermediate form specifies their PropType attribute as Atom. Just to be tricky, I stuck a nonsense property in the code called ArrayDemo, which has nothing to do with an Address, but is there to illustrate the concept of handling arrays.

Now we're getting somewhere! You have a generic set of classes and a serializable intermediate format that fully describes an Address object. The only thing left to do is create a way to transform that XML into fully functional C# output similar to the sample file.



Comment and Contribute

 

 

 

 

 


(Maximum characters: 1200). You have 1200 characters left.

 

 

Sitemap