A Little Chat About Code Sensible Code Generation

At work we all try and do our best to be productive. And as software developers we all probably have to author classes, that follow a familiar pattern. So if like me you are looking to work smart, you will undoubtedly look into code that writes code.

This is good in my opinion, code generation can save a LOT of time. But the cardinal rule of clever code generation is never ever having to touch the generated code once it has been generated. If you do have to make changes by hand to a generated file, its all wrong and the entire process can no longer be trusted.

Now this perplexed me for a while as I had a very real demand to perform some calculations when a property changes. Now this would be cool and could possibly be made part of the generated code if you know about it in advance and have the correct knowledge to instruct your code generator about this in advance. We neither knew about these requirements up front or were able to build enough intelligence into our code generator to deal with this sort of this.

So you can imagine that our business analyst came along and said “when property X changes, use property Y and Z to work out Q”. Mmmmm.

This would require me to do something when a property changes, this would mean manually changing my auto generated code. Oh no.

So I had a think about this, and luckily C# being the lovely language that it is has the answers. Partial classes / methods. All we need to do is provide the stubs in the generated code portion and provide hooks that the manually created partial class can use.

Here is an example.

The code generated part

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Text;
   5:  
   6:  namespace NiceCodeGenStylee
   7:  {
   8:      public partial class Class1
   9:      {
  10:  
  11:          private Int32 number1 = 0;
  12:          private Int32 number2 = 0;
  13:  
  14:          /// <summary>
  15:          /// When this method in called the hand cranked 
  16:          /// code should have the implementation
  17:          /// </summary>
  18:          partial void Number1PropertyChanging(
                            IntReportChanges args);
  19:  
  20:          /// <summary>
  21:          /// When this method in called the hand cranked 
  22:          /// code should have the implementation
  23:          /// </summary>
  24:          partial void Number1CodePropertyChanged(
                            Int32ItemChanges args);
  25:  
  26:  
  27:          /// <summary>
  28:          /// When this method in called the hand cranked 
  29:          /// code should have the implementation
  30:          /// </summary>
  31:          partial void Number2PropertyChanging(
                            IntReportChanges args);
  32:  
  33:          /// <summary>
  34:          /// When this method in called the hand cranked 
  35:          /// code should have the implementation
  36:          /// </summary>
  37:          partial void Number2CodePropertyChanged(
                            Int32ItemChanges args);
  38:  
  39:  
  40:          /// <summary>
  41:          /// Number1 property
  42:          /// </summary>
  43:          public Int32 Number1
  44:          {
  45:              get { return number1; }
  46:              set
  47:              {
  48:                  //is it different
  49:                  if(value != number1)
  50:                  {
  51:                      Int32 oldValue = number1;
  52:  
  53:                      //what are the changes
  54:                      Int32ItemChanges possibleValueChanges
  55:                          = new Int32ItemChanges(
                                      oldValue, value);
  56:  
  57:                      //report the possible change, allows non generated file to 
  58:                      //respond to the change, or cancel it
  59:                      IntReportChanges changes =
  60:                          new IntReportChanges
  61:                          {
  62:                              Values = possibleValueChanges
  63:                          };
  64:                      Number1PropertyChanging(changes);
  65:  
  66:                      //if there was no cancellation
  67:                      if (!changes.Cancel)
  68:                      {
  69:                          number1 = value;
  70:                          //report the change, allows non generated file to 
  71:                          //respond to the change
  72:                          Number1CodePropertyChanged(
                                   possibleValueChanges);
  73:                      }
  74:                  }
  75:  
  76:              }
  77:          }
  78:  
  79:          /// <summary>
  80:          /// Number2 property
  81:          /// </summary>
  82:          public Int32 Number2
  83:          {
  84:              get { return number2; }
  85:              set
  86:              {
  87:                  //is it different
  88:                  if (value != number2)
  89:                  {
  90:                      Int32 oldValue = number2;
  91:  
  92:                      //what are the changes
  93:                      Int32ItemChanges possibleValueChanges
  94:                          = new Int32ItemChanges(oldValue, value);
  95:  
  96:                      //report the possible change, allows non generated file to 
  97:                      //respond to the change, or cancel it
  98:                      IntReportChanges changes =
  99:                          new IntReportChanges
 100:                          {
 101:                              Values = possibleValueChanges
 102:                          };
 103:                      Number1PropertyChanging(changes);
 104:  
 105:                      //if there was no cancellation
 106:                      if (!changes.Cancel)
 107:                      {
 108:                          number2 = value;
 109:                          //report the change, allows non generated file to 
 110:                          //respond to the change
 111:                          Number1CodePropertyChanged(
                                   possibleValueChanges);
 112:                      }
 113:                  }
 114:  
 115:              }
 116:          }
 117:  
 118:  
 119:      }
 120:  }

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, “Courier New”, courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }Where the manually created code part, can use these partial method stubs to do calculations when a property changes or even cancel a changing property based on some condition.

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Text;
   5:  
   6:  namespace NiceCodeGenStylee
   7:  {
   8:      partial class Class1
   9:      {
  10:          /// <summary>
  11:          /// When this method in called the hand cranked 
  12:          /// code should have the implementation
  13:          /// </summary>
  14:          partial void Number2PropertyChanging(
                   IntReportChanges args)
  15:          {
  16:              if (args.Values.NewValue == 0)
  17:              {
  18:                  Console.WriteLine(
  19:                      "Number2 is invalid value, cancelling");
  20:                  args.Cancel = true;
  21:              }
  22:          }
  23:  
  24:          /// <summary>
  25:          /// When this method in called the hand cranked 
  26:          /// code should have the implementation
  27:          /// </summary>
  28:          partial void Number2CodePropertyChanged(
                   Int32ItemChanges args)
  29:          {
  30:              Console.WriteLine(String.Format(
  31:                  "Number2 changed to {0}, doing calcs",
                       args.NewValue));
  32:              DoNumberCalcs();
  33:          }
  34:  
  35:  
  36:          /// <summary>
  37:          /// When this method in called the hand cranked 
  38:          /// code should have the implementation
  39:          /// </summary>
  40:          partial void Number1PropertyChanging(
                   IntReportChanges args)
  41:          {
  42:              if (args.Values.NewValue == 0)
  43:              {
  44:                  Console.WriteLine(
  45:                      "Number1 is invalid value, cancelling");
  46:                  args.Cancel = true;
  47:              }
  48:          }
  49:  
  50:          /// <summary>
  51:          /// When this method in called the hand cranked 
  52:          /// code should have the implementation
  53:          /// </summary>
  54:          partial void Number1CodePropertyChanged(
                   Int32ItemChanges args)
  55:          {
  56:              Console.WriteLine(String.Format(
  57:                  "Number1 changed to {0}, doing calcs",
                       args.NewValue));
  58:              DoNumberCalcs();
  59:          }
  60:  
  61:  
  62:          private void DoNumberCalcs()
  63:          {
  64:              Console.WriteLine(
  65:                  String.Format("Number1 ({0}) + Number2 ({1}) ={2}",
  66:                      number1, number2, number1 + number2));
  67:          }
  68:      }
  69:  }

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, “Courier New”, courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }Where these little helper classes are used

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Text;
   5:  
   6:  namespace NiceCodeGenStylee
   7:  {
   8:      struct Int32ItemChanges
   9:      {
  10:          public readonly Int32 OldValue;
  11:          public readonly Int32 NewValue;
  12:  
  13:          public Int32ItemChanges(
                   Int32 oldValue, Int32 newValue)
  14:          {
  15:              OldValue = oldValue;
  16:              NewValue = newValue;
  17:          }
  18:      }
  19:  
  20:  }

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, “Courier New”, courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Text;
   5:  
   6:  namespace NiceCodeGenStylee
   7:  {
   8:      class IntReportChanges
   9:      {
  10:          public Int32ItemChanges Values { get; set; }
  11:          public Boolean Cancel { get; set; }
  12:      }
  13:  }

.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, “Courier New”, courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }And here is a demo of it running

image

See it all works lovely.

Here is a small demo project for your enjoyment : nicecodegenstylee.zip

Advertisements

5 thoughts on “A Little Chat About Code Sensible Code Generation

  1. Tyler Thalman says:

    Sasha,

    I was just pondering this problem myself with a LinqToSql datacontext getting generated by sqlmetal, now if only there was a way to have sqlmetal throw in the partial methods 🙂

  2. sacha says:

    Tyler

    LINQ2SQL does a grand job of providing these hooks. For example here is a LINQ2SQL class that is in the .designer.cs part of the LINQ2SQL dbml file for a MP3 class in my own DB.

    #region Extensibility Method Definitions
    partial void OnLoaded();
    partial void OnValidate(System.Data.Linq.ChangeAction action);
    partial void OnCreated();
    partial void OnIDChanging(long value);
    partial void OnIDChanged();
    partial void OnFileNameChanging(string value);
    partial void OnFileNameChanged();
    partial void OnAlbumChanging(string value);
    partial void OnAlbumChanged();
    partial void OnArtistChanging(string value);
    partial void OnArtistChanged();
    partial void OnGenreNameChanging(string value);
    partial void OnGenreNameChanged();
    partial void OnTitleChanging(string value);
    partial void OnTitleChanged();
    #endregion

    This gives you all the hooks you could possibly need.

  3. […] A Little Chat About Code Sensible Code Generation (Sacha Barber) […]

  4. Pramod.P.V says:

    This is a real good feature that people normally overlook. Nice to have you post about it.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: