Growcode Manual

  1. Overview of a Growcode Object Model
  2. Input handled by Growcode
  3. Invoking growcode.exe
  4. The Utility Namespace
  5. Supported data types
  6. Classes
  7. Namespaces
  8. Enums
  9. Lists
  10. Dictionaries
  11. Constructors
  12. Initial values
  13. Events
  14. Undo/redo
  15. Serialization
  16. Inheritance and double dispatch
  17. Readonly properties
  18. Model violations
  19. The Growcode grammar

This manual assumes the reader already knows the C# language.

Overview of a Growcode Object Model

Object models produced by Growcode are just like a normal object model you may write yourself, but as well as providing features like an event mechanism, the object model performs checks to ensure validity of the model.

Growcode produces object models that are acyclic graphs of objects. An object can have more than one parent, but any possible cycles are detected at compile time and an error is reported.

There are some restrictions on how an object model can be used, for example an instance of a class of a model may not be added to two different instances of the model. Also an object that has been removed from a model can no longer be modified (but can be cloned and the clone modified).

Input handled by Growcode

Growcode object models are defined using a very small subset of the C# syntax. This means a C# developer can begin working with Growcode very quickly, with no need to learn a new language or XML schema. Growcode can process non-nested namespaces, classes and enumerations. Classes can contain simple field declarations that are turned into properties. Here is an example object model definition showing most of the syntax features supported by Growcode:

    namespace Test.DrawingLib
    {
        class Drawing
        {
            List<Shape> Shapes;
        }
        
        abstract class Shape
        {
            Color Color;
            Point Position = null;
        }
        
        class Circle : Shape
        {
            double Radius;
        }
        
        class Square : Shape
        {
            double SideLength;
        }
        
        enum Color { Red, DarkRed, Green, DarkGreen, Blue, DarkBlue }
        
        class Point { int X = 0, Y = 0; }
    }

For a full description of the syntax supported by Growcode see the Grammar at the bottom of this manual.

Invoking growcode.exe

From the command line, Growcode can generate an object model from an input text file by invoking like this:

> growcode MyModel.txt

Where 'MyModel.txt' contains your object model definition. Providing there are no lexical or semantic errors in your object model definition the output will be an assembly called MyModel.dll

The Utility Namespace

The output DLL not only includes the enhanced versions of your input classes, but also includes a utility namespace. This utility namespace contains common interfaces and classes that the object model depends on and will more that likely be used in your own code. Some of these classes and interfaces are mentioned throughout the manual. The utility namespace name is made up of your assembly name and 'Util'. For example, if your assembly name is 'Drawing' the utility namespace inside Drawing.dll will be called 'DrawingUtil'.

Supported data types

In addition to user defined classes, enumerations and the collection types List<T> and Dictionary<K, V> here are the data types supported by Growcode, listed in both short and long form:

bool System.Boolean
byte System.Byte
sbyte System.SByte
char System.Char
decimal System.Decimal
double System.Double
float System.Single
int System.Int32
uint System.UInt32
long System.Int64
ulong System.UInt64
short System.Int16
ushort System.UInt16
string System.String
Guid System.Guid
DateTime System.DateTime
TimeSpan System.TimeSpan

Classes

The enhanced classes generated for an object model look from the outside much the same as how they were defined in the object model definition file.

Fields in the model definition are converted into properties in the assembly. If a field was a collection type such as a List, then the property will only have a getter, the list being constructed when the container class object itself is constructed.

It is worth noting here that Growcode does not use access modifiers. So all classes, enums and fields defined in the object model definition do not need and do not use symbols such as 'public' or 'protected'.

If an object has been added to a model and then removed from the model it cannot be modified unless it is placed back into the model. Alternatively DeepClone can be called on the object and the copy can be modified outside the model.

Namespaces

Growcode handles namespaces in much the same way as C#. However nesting of namespaces is not supported. A similar effect can be achieved by specifying multi part namespace names such as:

    namespace Company.Department.Product
    {
        class X { }
    }
    
    namespace Company.General
    {
        class Y { }
    }

Enums

Growcode supports the basic C# enum syntax. Specifying values of enum elements is not supported. Here is an example of a valid Growcode enum:

    enum Day
    {
        Mon, Tue, Wed, Thu, Fri,
        Sat, Sun
    }
    

Lists

Class members of type List<T> are exposed in the object model through an interface calledIObservableList<T>. This interface inherits from System.Collections.Generic.IList<T> so all the normal list behaviour is available. In addition events are provided for when items are added, removed, cleared and replaced in the list.

public interface IObservableList<T> : System.Collections.Generic.IList<T>
{
    event ListItemsAddedEventHandler<T> ItemsAdded;
    event ListItemsRemovedEventHandler<T> ItemsRemoved;
    event ListClearedEventHandler<T> ItemsCleared;
    event ListItemReplacedEventHandler<T> ItemReplaced;
}

Lists can contain any primitive type or any user defined class or enumeration. However only one level of nesting is supported, so while this is fine:

    List<string>

This is not supported:

    List<List<string>>

Dictionaries

Class members of type Dictionary<K, V> are exposed in the object model through an interface calledIObservableDictionary<K, V>. This interface inherits from System.Collections.Generic.IDictionary<K, V> so all the normal dictionary behaviour is available. In addition events are provided for when entries are added, removed, cleared and replaced in the dictionary.

public interface IObservableDictionary<K, V> : System.Collections.Generic.IDictionary<K, V>
{
    event DictionaryEntriesAddedEventHandler<K, V> EntriesAdded;
    event DictionaryEntriesRemovedEventHandler<K, V> EntriesRemoved;
    event DictionaryEntriesClearedEventHandler<K, V> EntriesCleared;
    event DictionaryEntryValueReplacedEventHandler<K, V> EntryValueReplaced;
}

Dictionaries can contain any primitive type or any user defined class or enumeration. However only one level of nesting is supported, so while this is fine:

    Dictionary<int, string>

This is not supported:

    Dictionary<List<string>, string>

User defined classes can be used as keys in dictionaries however it should be noted that user defined classes in the object model do not implement value equality or a hash function so the default .NET behaviour is used for Equals and GetHashCode. In other words, user defined classes used as keys only match on reference equality.

Constructors

Each user defined class in an object model is given a constructor that takes as parameters all members of the class that do not have initial values specified, plus all members of all inherited classes that do not have initial values specified. In addition the top level class in the object model has a constructor that takes a System.IO.TextReader. This constructor when called loads the object model from XML.

Initial values

Class members can be given initial values, which has the effect that the member is not included as a parameter of the constructor of the class. The way a member is initialized depends on its type.

Boolean members are initialized with true of false like so:

    bool B = false;

Numeric types are initialized in the same way as in C#:

    int X = 100;
    float F = 100.123F;
    decimal D = 1.123M;

User defined class members can be given the value of null:

    class X { }
    
    class Y
    {
        X M = null;
    }
    

Other types such as enumerations can be given a default value using the default keyword:

    enum TimeFormat { AM, PM }
    
    class Time
    {
        TimeFormat Format = default(TimeFormat);
    }
    

Events

Each class member in a Growcode object model has an associated event, for example, this class Person:

    class Person
    {
        int Age;
        string Name;
    }

Will have two events 'AgeChanged' and 'NameChanged'. In this example, given T is the type of the member, the event handler will be of type ItemChangedEventHandler<T> and the System.EventArgs derived type will be of type ItemChangedEventArgs<T>.

Inside an event handler the changed item can be examined through:

    T ItemChangedEventArgs.Item { get; }

Or the old value of the item through:

    T ItemChangedEventArgs.OldItem { get; }

If the 'changed' event on the parent(s) of the changed object should not be called after the 'changed' event actually on the changed object, then the method below should be invoked from inside the event handler:

    void ItemChangedEventArgs.StopEventBubbling()

As there are no cycles in Growcode object models, events can safely bubble up through a changed objects parent nodes until they finally fire the Changed event on the top level object in the model.

At any stage in the bubbling up of an event, an event handler can stop the bubbling process by calling StopEventBubbling() on the System.EventArgs derived object passed to the event handler.

This gives flexibility in how your program handles events. If in your program you do not need to know that a specific member of an object has changed, but just need to know that the object has changed in some way, just hook your event handler to the object's Changed event on the parent of the object.

Events bubble up through lists and dictionaries in the same way as through any other object in the model. But lists and dictionaries provide extra, more specific sets of events as well. See Lists or Dictionaries for more information.

When using change sets it is important to understand that an event is not fired for every object changed in the set. Instead a single 'changed' event is fired on the lowest common ancestor of all objects in the model that have been changed by the change set. This event is fired when the change set is committed.

Undo/redo

The top-level class in a Growcode object model contains several methods and properties to support undo and redo. All changes in the object model are tracked whether it is simply assigning a new primitive value to a property, or modifying a more complex data structure such as a list of user-defined composite objects.

Methods available for undo/redo handling:

    void Undo()

Reverts the object model to the state before the last change was made. The property CanUndo should be checked before this method is called, otherwise a ModelViolationException may be thrown.

    void Redo()

Reverts the object model to the state before the last undo was made. The property CanRedo should be checked before this method is called, otherwise a ModelViolationException may be thrown.

    void RecordChangeSet(string changeDescription)

Starts recording an atomic change to the object model. The 'changeDescription' should be a description of the whole change made to the model by a series of individual model changes. Will throw aModelViolationException if the object model is already recording a change set.

    bool TryRecordChangeSet(string changeDescription)

Attempts to start recording an atomic change to the object model. The 'changeDescription' should be a description of the whole change made to the model by a series of individual model changes. Will return false if the object model is already recording a change set. In this case nothing is done.

    void CommitChangeSet()

Ends the recording of an atomic change to an object model. This method actually applies the change to the model. This method fires a 'changed' event on the lowest common ancestor of all objects in the model that have been changed by the change set.

    void CancelChangeSet()

Ends the recording of an atomic change to an object model. Discards any changes that were queued, in anticipation of being applied to the model.

    IAutoChangeSet AutoChangeSet(string changeDescription)

Starts recording an atomic change to the object model. The 'changeDescription' should be a description of the whole change made to the model by a series of individual model changes. The returned IAutoChangeSet interface inherits from System.IDisposable, therefore can be placed in a C# 'using' statement to ensure any changes made in the change set are rolled back if an exception is thrown whilst making the changes. It should be noted that calling IAutoChangeSet.Commit() is required once all changes have been made and before the IAutoChangeSet object is disposed.

Properties available for undo/redo handling:

    bool CanUndo { get; }

Determines whether there are any changes that can be undone by a call to Undo()

    bool CanRedo { get; }

Determines whether there are any changes that can be redone by a call to Redo()

    string UndoDescription { get; }

The description of the change on the top of the undo stack. If this change was made with a change set, then the user supplied description of the change will be returned. If the change was made outside of a change set, through ordinary manipulation of the object model, then a generic message for the change will be returned prefixed by 'growcode:'. The actual message returned is subject to change between versions of Growcode, but all change messages that are not user supplied will always be prefixed with 'growcode:'.

    string RedoDescription { get; }

The description of the change on the bottom of the redo stack. The same description rules as for 'UndoDescription' apply.

    int UndoStackSize { get; }

The total number of changes in the undo/redo stack. Will always be less than or equal to 'MaxUndoStackSize'.

    int MaxUndoStackSize { get; set; }

The maximum number of changes that will be stored in the undo/redo stack. The default for this value is System.UInt32.MaxValue so should be reduced to avoid excessive memory consumption. A value of 0 is valid, however this disables a feature of the event mechanism which allows inspection of old values of changed objects in event handlers. Therefore, if undo/redo is not required for you application a MaxUndoStackSize of 1 may still be useful.

Serialization

The top level class of an object model contains a constructor that takes a System.IO.TextReader which can be used to load an object model from XML.

To save an object model to XML the top level class provides:

    void Save(System.IO.TextWriter tw)

Inheritance and double dispatch

Classes can inherit other classes using the standard C# syntax. If a class has sub classes defined, an interface that handles double dispatch to those subclasses from the base class will be generated. For example in the Drawing model defined above there will be an interface generated in the same namespace as the Shape class. The interface will be called IShapeHandler and contain dispatch methods for each subclass of Shape. This allows code such as:
    Drawing d = new Drawing();
    
    d.Shapes.Add(new Circle(3.0, Color.DarkRed));
    d.Shapes.Add(new Square(5.5, Color.Green));
    
    IShapeHandler printShape = new PrintShape();
    foreach (Shape s in d.Shapes)
        s.Dispatch(printShape);

Where PrintShape might be defined as:

    class PrintShape : IShapeHandler
    {
        public void Handle(Square square)
        {
            Console.WriteLine("Square of side length: " + square.SideLength);
        }
    
        public void Handle(Circle circle)
        {
            Console.WriteLine("Circle of radius: " + circle.Radius);
        }
    }

This approach of using dispatch interfaces allows your compiler to be used to detect what parts of your code require updating when a new sub class is added to the object model as every implementation of a dispatch interface requires all subclasses to be handled.

Readonly properties

Class members, except for generic collections can all optionally specify the 'readonly' modifier, for example:

    readonly double D;

This causes the generated property that represents the member to only have a getter. The value of the member can still be initialized on the classes constructor. However if a member was specified as readonly and had an initial value such as:

    readonly int I = 2;

Then this is equivalent to a constant.

Read-only members do not have an associated 'changed' event unless they are of a user defined class type, in which case the 'changed' event is still generated because members of that user defined class type could modify the object and cause the event to fire. So in this case only the object reference, specified on construction, is said to be read-only.

Model violations

Under certain situations, if the object model is used in an unsupported way a ModelViolationException may be thrown. This exception is defined in the object models Utility Namespace.

This exception should be considered an assertion to be fixed in the client code, rather than an exception to be trapped and handled.

Here is a list of reasons this exception may be thrown:

  • Calling Undo() when there is nothing to undo
  • Calling Redo() when there is nothing to redo
  • Adding the same object to more than one model
  • Modifying an object after it has been removed from a model
  • Committing a change set twice
  • Committing a change set after an IAutoChangeSet has been disposed
  • Recording a change set when one is already being recorded
  • Reading an old value in an event handler after a change has been made to the model
  • Reading an old value in an event handler when the maximum undo stack size is set to zero
  • Reading an old value in an event handler when the item is not contained in a model

The Growcode grammar

Here is an informal and abbreviated grammer for the input Growcode can handle:

            growcode-file:
                namespace
                class
                enum
            
            namespace:
                'namespace' identifier '{' namespace-member '}'
            
            namespace-member:
                class
                enum
            
            class:
                abstract-modifier 'class' identifier sub-class '{' members '}'
            
            abstract-modifier:
                empty-string
                'abstract'
                
            members:
                empty-string
                member members
                
            member:
                readonly-modifier type identifier member-initilizer ';'
            
            readonly-modifier:
                empty-string
                'readonly'
                
            member-initilizer:
                empty-string
                '=' initial-value
                
            intial-value:
                number
                'default' '(' type ')'
                'null'
            
            enum:
                'enum' identifier '{' enum-member enum-members '}'
            
            enum-members:
                empty-string
                ',' enum-member enum-members
                
            enum-member:
                identifier
                
            type:
                identifier
                builtin-type
                
            subclass:
                empty-string
                ':' identifier
            
            identifier:
                (_|[a-z]|[A-Z])(_|[a-z]|[A-Z]|[0-9]|\.)*
            
            number:
                (C# numeric literal possibly with suffix)
                
            builtin-type:
                generic-type
                primitive-type
            
            generic-type
                List '<' type '>'
                Dictionary '<' type ',' type '>'
            
            primitive-type:
                int
                double
                (all other C# builtin types)
                DateTime
                TimeSpan
                Guid
Comments