Search This Blog

Tuesday, March 31, 2009

INotifyPropertyChanged made easier using PostSharp

How many times have you written a C# business class like the one below only to realise that if you want the UI to react to changes you need to implement this cumbersome INotifyPropertyChanged interface.

So the above class will provide no change notification, you’ll have to implement the interface after which your code will look something like this (Note that this is .NET 2.0 syntax):

public class Customer : System.ComponentModel.INotifyPropertyChanged 
{
private String _Name;
private DateTime _DOB;

public String Name
{
get
{
return _Name;
}
set
{
_Name = value;
OnPropertyChanged("Name");
}
}

public DateTime DOB
{
get
{
return _DOB;
}
set
{
_DOB = value;
OnPropertyChanged("DOB");
}
}

#region INotifyPropertyChanged Members

public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;

protected void OnPropertyChanged(String propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}

#endregion
}

That class already looks extremely overloaded and ugly…now imagine if you are able to use .NET 3 or 3.5 and you want to utilise it’s extremely cool feature of auto-implemented properties (e.g. public int Age { get; set; }). Guess what…you can’t!!!! Because where would you put your calls to OnPropertyChanged(…)?

This is where PostSharp saves you.

The above class, with auto-implemented properties and the INotifyPropertyChanged interface.

public class Customer : System.ComponentModel.INotifyPropertyChanged 
{
[Notify]
public String Name { get; set; }

[Notify]
public DateTime DOB { get; set; }

#region INotifyPropertyChanged Members

public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;

protected void OnPropertyChanged(String propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}

#endregion
}

That’s cool…from ~50 lines of code to ~25 lines of code. You’ll be wondering, how will the PropertyChanged event fire now? The answer is within the [Notify] attribute. This attribute is a special attribute which inherits from the PostSharp class OnMethodBoundaryAspect. For this you’ll have to add references to the PostSharp DLLs.


image 



The implementation is shown below:

[Serializable]
[global::System.AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public sealed class NotifyAttribute : OnMethodBoundaryAspect
{
public override void OnExit(MethodExecutionEventArgs eventArgs)
{
if (eventArgs == null)
return;

//Why are property sets not properties? they are methods?
if ((eventArgs.Method.MemberType & System.Reflection.MemberTypes.Method) == System.Reflection.MemberTypes.Method && eventArgs.Method.Name.StartsWith("set_"))
{
Type theType = eventArgs.Method.ReflectedType;
string propertyName = eventArgs.Method.Name.Substring(4);

// get the field storing the delegate list that are stored by the event.
FieldInfo[] fields = theType.GetFields(BindingFlags.Instance | BindingFlags.NonPublic);
FieldInfo field = null;
foreach (FieldInfo f in fields)
{
if (f.FieldType == typeof(PropertyChangedEventHandler))
{
field = f;
break;
}
}

if (field != null)
{
// get the value of the field
PropertyChangedEventHandler evHandler = field.GetValue(eventArgs.Instance)as PropertyChangedEventHandler;

// invoke the delegate if it's not null (aka empty)
if (evHandler != null)
{
evHandler.Invoke(eventArgs.Instance, new PropertyChangedEventArgs(propertyName));
}
}
}
}
}

This works perfectly, on every method “exit” the above OnExit(…) method will fire and invoke the PropertyChangedEventHandler for that particular property with it’s property name.


Neat isn’t it? Let us run some tests against it to make sure that it is actually happening. For this we revert to NUnit.


[TestFixture]
public class CustomerTest
{
private Customer _customer;
private String _propertyChanged;

[SetUp]
protected void Setup()
{
_customer = new Customer();
_customer.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(Customer_PropertyChanged);
}

[TearDown]
protected void TearDown()
{
_propertyChanged = String.Empty;
}

void Customer_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
_propertyChanged = e.PropertyName;
}

[Test]
public void TestNotifyName()
{
_customer.Name = "Ruskin";
Assert.AreEqual("Name", _propertyChanged);
}

[Test]
public void TestNotifyDOB()
{
_customer.DOB = DateTime.Now;
Assert.AreEqual("DOB", _propertyChanged);
}
}

image 
You can go one step further…ever had calculated properties in your business classes, for e.g. if you have a property for “Age” which gets calculated depending on the DOB, therefore, if DOB changes, Age changes. In our current implementation we can only mark properties to fire PropertyChanged events for themselves, but we can’t make them trigger events for other properties. This can be easily solved by adding another standard .NET attribute shown below:

[Serializable] 
[global::System.AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public sealed class NotifyChildrenAttribute : Attribute
{
public NotifyChildrenAttribute(params string[] dependents)
{
Dependents = dependents;
}

public NotifyChildrenAttribute() { }

public string[] Dependents { get; set; }
}

Now we add an “Age” property to our Customer class and let us look at the changed version: 

public class Customer : System.ComponentModel.INotifyPropertyChanged
{
[Notify]
public String Name { get; set; }

[Notify]
[NotifyChildren("Age")]
public DateTime DOB { get; set; }

public int Age { get { return DateTime.Now.Year - DOB.Year; } }

#region INotifyPropertyChanged Members

public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;

protected void OnPropertyChanged(String propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}

#endregion
}

As we can see, we have marked our DOB property to fire a PropertyChanged event not only for itself but also for the Age property. We still need to modify our implementation of the OnExit(…) method slightly to cater for this new attribute. Our implementation will look as follows:

[Serializable]
[global::System.AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public sealed class NotifyAttribute : OnMethodBoundaryAspect
{
public override void OnExit(MethodExecutionEventArgs eventArgs)
{
if (eventArgs == null)
return;

//Why are property sets not properties? they are methods?
if ((eventArgs.Method.MemberType & System.Reflection.MemberTypes.Method) == System.Reflection.MemberTypes.Method && eventArgs.Method.Name.StartsWith("set_"))
{
Type theType = eventArgs.Method.ReflectedType;
string propertyName = eventArgs.Method.Name.Substring(4);

List<string> allPropertiesThatNeedNotification = new List<string>();
if (!String.IsNullOrEmpty(propertyName))
{
PropertyInfo pInfo = theType.GetProperty(propertyName);
if (pInfo != null)
{
// get all the properties which may be affected
object[] notifyChildren = pInfo.GetCustomAttributes(typeof(NotifyChildrenAttribute), false);
foreach (var item in notifyChildren)
{
if (item is NotifyChildrenAttribute)
{
NotifyChildrenAttribute att = item as NotifyChildrenAttribute;
allPropertiesThatNeedNotification.AddRange(att.Dependents);
}
}
}
allPropertiesThatNeedNotification.Add(propertyName);
}

// get the field storing the delegate list that are stored by the event.
FieldInfo[] fields = theType.GetFields(BindingFlags.Instance | BindingFlags.NonPublic);
FieldInfo field = null;
foreach (FieldInfo f in fields)
{
if (f.FieldType == typeof(PropertyChangedEventHandler))
{
field = f;
break;
}
}

if (field != null)
{
// get the value of the field
PropertyChangedEventHandler evHandler = field.GetValue(eventArgs.Instance) as PropertyChangedEventHandler;

// invoke the delegate if it's not null (aka empty)
if (evHandler != null)
{
foreach (var item in allPropertiesThatNeedNotification)
{
evHandler.Invoke(eventArgs.Instance, new PropertyChangedEventArgs(item));
}
}
}
}
}
}

Updating the tests…note that our previously defined tests won’t work now because for every singular property changed there may be a series of PropertyChanged events, we need some kind of collection which we need to test against. This testing is a little bit primitive but this is only for demonstration purposes.

[TestFixture]
public class CustomerTest
{
private Customer _customer;
private List<String> _propertiesChanged;

[SetUp]
protected void Setup()
{
_customer = new Customer();
_customer.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(Customer_PropertyChanged);
}

[TearDown]
protected void TearDown()
{
_propertiesChanged = new List<string>();
}

void Customer_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
Assert.IsTrue(_propertiesChanged.Contains(e.PropertyName), String.Format("Property {0} is not contained within the list", e.PropertyName));
}

[Test]
public void TestNotifyName()
{
_propertiesChanged.Add("Name");
_customer.Name = "Ruskin";
}

[Test]
public void TestNotifyDOB()
{
_propertiesChanged.Add("DOB");
_propertiesChanged.Add("Age");
_customer.DOB = DateTime.Now;
}
}

image 


Downloads: PostSharp

Monday, March 30, 2009

Common InstallShield functions and their Usage

Getting a property string

   1: NUMBER nvBufferLen;
   2: STRING svInstallDir;
   3: MsiGetProperty(hMSI, "INSTALLDIR", svInstallDir, nvBufferLen);

Where:
hMSI = handle of the MSI (gets automatically passed into functions)
INSTALLDIR = The name of the property to get
svInstallDir = The variable in which to store the returned property value
nvBufferLen = The length of the buffer, often set to 255.


Setting a property string 

   1: MsiSetProperty(hMSI, "INSTALLDIR", "C:\Program Files\InstallationDirectory");

Where:
hMSI = handle of the MSI (gets automatically passed into functions)
INSTALLDIR = The name of the property to set
Value = The value of the property to set to


Converting a number to string and vice versa

   1: number nTemp;
   2: string svTemp; 
   3: nTemp = 25;
   4:  
   5: NumToStr(svTemp, nTemp); // 25 -> "25" 
   6: StrToNum(nTemp, svTemp); // "25" -> 25

String comparison

   1: if (StrCompare("String A", "String B") == 0) then
   2:     // do something here
   3: endif;

String concatenation

   1: STRING svTemp;
   2: Sprintf(svTemp,  
   3:     "A string taking two parameters %s %s",
   4:     "Parameter 1", "Parameter 2");

String Trim(…) (Extracted from http://community.installshield.com/showthread.php?t=24423)

   1: function Trim(aString)   
   2:     BOOL done;    
   3:     NUMBER startIndex;   
   4:     NUMBER endIndex;    
   5:     NUMBER aByte;
   6: begin    
   7:     startIndex = 0;  
   8:     endIndex = StrLength( aString ) - 1;
   9:     done = ( startIndex > endIndex );
  10:     while ( !done )
  11:         GetByte( aByte, aString, startIndex );        
  12:         if ( aByte = ASCII_SPACE ) then
  13:             startIndex = startIndex + 1;
  14:             done = ( startIndex > endIndex );
  15:         else
  16:             done = TRUE;
  17:         endif;
  18:     endwhile;
  19:     done = ( startIndex > endIndex );
  20:  
  21:     while ( !done )
  22:         GetByte( aByte, aString, endIndex );
  23:         if ( aByte = ASCII_SPACE ) then
  24:             endIndex = endIndex - 1;
  25:             done = ( startIndex > endIndex );
  26:         else
  27:             done = TRUE;
  28:         endif;
  29:     endwhile;
  30:  
  31:     if ( startIndex > endIndex ) then
  32:         aString = "";
  33:     else
  34:         StrSub( aString, aString, startIndex, endIndex - startIndex + 1 );    
  35:     endif;
  36:     return 0;
  37: end;


Reading XML using MS XML DOM 6 (Inspired by http://community.installshield.com/showthread.php?t=101600&page=2)


   1: OBJECT oDoc, oNode, oNodeList;
   2: NUMBER nvBufferLen, nTemp;
   3: STRING svXPathQuery, svValue, svTemp, svMessage, svError, svLine, svLinePos;
   4:  
   5: set oDoc = CreateObject("Msxml2.DOMDocument.6.0");  
   6: if (IsObject(oDoc) = FALSE) then
   7:     MessageBox("MSXML DOM-Document Connection Failed", 0);
   8:     return -1;
   9: endif;
  10:       
  11: oDoc.async = FALSE;  
  12: oDoc.setProperty("SelectionLanguage", "XPath");
  13: oDoc.load( svInstallDir ^ UI_APP_CONFIG );
  14:  
  15: if ( oDoc.parseError.errorCode != 0 ) then
  16:     NumToStr ( svError , oDoc.parseError.errorCode );
  17:     NumToStr ( svLine , oDoc.parseError.line );
  18:     NumToStr ( svLinePos , oDoc.parseError.linepos );
  19:     MessageBox ( "The XML File did not load correctly.\n" + 
  20:                  "Error Code: " + svError + "\n" +
  21:                  "Error Line: " + svLine + "\n" + 
  22:                  "Error Line Position: " + svLinePos + "\n" +
  23:                  oDoc.parseError.reason , 0 );
  24: else 
  25:     svXPathQuery = "//XPathQuery";
  26:  
  27:     try 
  28:         set oNodeList = objDOMDOC.selectNodes( strXPathQuery );
  29:     catch
  30:         nTemp = Err.Number; 
  31:         NumToStr(svTemp, nTemp);
  32:         MessageBox("Error while selecting " + strXPathQuery + " (Error + " + svTemp + ")", 0);
  33:     endcatch;
  34:     
  35:     if (oNodeList.length == 0) then
  36:        MessageBox( "No " + strXPathQuery + " elements found.", 0 );
  37:     else 
  38:         for i = 0 to ( oNodeList.length - 1 );
  39:             set oNode = oNodeList.nextNode;
  40:             try
  41:                 svValue= oNode.text;
  42:                 //svValue = oNode.attributes.getNamedItem( strAttributeName ).value; // to get attribute value
  43:             catch
  44:                 MessageBox("No value found for " + strXPathQuery, 0);
  45:             endcatch;
  46:         endfor;
  47:     endif;
  48: endif;

Bhai Log indoor cricket game (Grading game)

Our virgin game night (first game for our indoor season)…and in all honesty that’s exactly how we played smile_sad.

The game was highly anticipated by most players but especially me as I was captaining our squad this season after giving the veteran captain (Jacinth) a rest.  Big shoes to fill considering we won the last season we played when Jacinth was captain.

It did not start well for me…our first game and I had to sit out due to sickness but nonetheless we had enough players.  Luckily it was a grading game so it wasn’t that important.

Everyone got to the game in time…this is a rarity as we all follow IST (Indian Standard Time smile_wink) except this one dude who apparently forgot we had a game…Yes you know who you are smile_regular.  But then we did get a hold of him and managed to get him down to the venue in time.

Our batting performance was good, getting 140ish in the first game is not a bad effort at all but we were let down in the fielding department.  Not to mention that we nearly won the game too…the other team needed one run off the last ball to win as the scores was tied…it was a dot ball which the keeper struggled with (who can blame him…king does bowl which such sheer pace).  The batsmen had time to take a single and win the game…

Vengeance will be in our eyes next time smile_nerd…opps wrong emoticon…I meant smile_shades

Change

I have read quite a few blogs of people mentioning this word.  I am not going to provide a definitive answer and neither am I trying to “change” your viewpoint of what “change” is….I used the word “change” three times already without even analysing exactly of its definition.

What is change?

As per Google:
Definitions of change on the Web:

  • cause to change; make different; cause a transformation; "The advent of the automobile may have altered the growth pattern of the city"; "The discussion has changed my thinking about the issue"
  • an event that occurs when something passes from one state or phase to another; "the change was intended to increase sales"; "this storm is ...
  • undergo a change; become different in essence; losing one's or its original nature; "She changed completely as she grew older"; "The weather changed last night"
  • a relational difference between states; especially between states before and after some event; "he attributed the change to their marriage"
  • become different in some particular way, without permanently losing one's or its former characteristics or essence; "her mood changes in accordance with the weather"; "The supermarket's selection of vegetables varies according to the season"
  • the action of changing something; "the change of government had no impact on the economy"; "his change on abortion cost him the election"
  • switch: lay aside, abandon, or leave for another; "switch to a different brand of beer"; "She switched psychiatrists"; "The car changed lanes"
  • the result of alteration or modification; "there were marked changes in the lining of the lungs"; "there had been no change in the mountains"
  • change clothes; put on different clothes; "Change before you go to the opera"
  • the balance of money received when the amount you tender is greater than the amount due; "I paid with a twenty and pocketed the change"
  • exchange or replace with another, usually of the same kind or category; "Could you convert my dollars into pounds?"; "He changed his name"; "convert centimeters into inches"; "convert holdings into shares"
  • a thing that is different; "he inspected several changes before selecting one"
  • exchange: give to, and receive from, one another; "Would you change places with me?"; "We have been exchanging letters for a year"
  • a different or fresh set of clothes; "she brought a change in her overnight bag"
  • transfer: change from one vehicle or transportation line to another; "She changed in Chicago on her way to the East coast"
    wordnet.princeton.edu/perl/webwn

Rather complicated isn’t it…

How do I see change
Change is a mindset.  An inevitability which occurs sometimes even without you knowing.  You often change your mind, clothes, time changes, the weather changes, etc… Can you change something without actually changing it, absolutely! Well, that’s at least what I am getting at.  A change can be made simply by how you think about things.  You are changing your thinking and according to me that’s the most important kind of change and not to mention the most powerful change of all.  I do agree as humans we cannot change things which are out of our control…weather is a good example of that although if you can change a mind (your own or some else's) it is an amazing feat in a league of its own.  The greatest leaders in history, sporting legends, coaches, teachers are those who convince their disciples/students to change their way of thinking.  In effect changing their mind.  We are affecting free will, well not directly but nonetheless we are.

You must be thinking…blah blah blah, I know all of this which is true…everyone probably knows the above although this is not what the blog is about.  I am trying to determine and hopefully change (there the word CHANGE) the way you see “change”.

When you get faced with a change, how do you generally react?  I go through a range of feelings generally:

  • nervous
  • excited
  • anxious
  • scared
  • confidence

Now the above will definitely depend on whether the change was voluntary or involuntary and also how big a change is.  When you go through this emotions do you often take into consideration the implications of your change?  How often do you go through a “what-if” analysis trying out different paths of your decision making.  How often do you document or remember of what the reasoning behind your change was?  In all honesty I don’t think any person would go through this kind of analysis for a small to medium size changed…forget that…most people wouldn’t even do those kind of analysis for making bigger life changes (i.e. moving countries, changing jobs, marriage, etc).  Lets face it, we have big expectations from our brains, we attempt to remember the reasons for the change, completely possible to do…although how long will you remember this reasoning?

This forced me to think, can we make the process of change more reliable by remembering why and when we made a change.  Will this help us make better changes in the future?  This is a more generic solution, what I am currently in the process of doing is looking at this from a software developers perspective.

A software developer perspective…
Many a times have I done some work and come back to it and had trouble remember why and when the change was made.  If you work for a company surely they’ll have some sort of mechanism to document exactly this although more often than you think it will fall short of your personal expectations.  Maybe you are compulsive organiser like how I am and start writing endless pages of notes of why a change was required, where it was required, what other modules will the change effect, the tests you did to choose the optimum implementation of the change, etc etc.  Finally this will become hard to manage simply due to so much manual writing, sorting and especially searching back through pages of written work.  Isn’t there a better solution?

Surely there are better solutions out there…which you may have to pay for or other solutions which maybe completely generic and do not cater for your particular flavour of change management.  I really want something which I can use, modify and persist over years.  Something like a blog…but only for your coding changes.  This is when it came to me, I really want a visual DSL which can cater for my obtuse needs as they arise.  I can also use this wherever I go irrespective of the change I am making.

This system could not only help you document what change you made but also help you in determining how successful your change was 1-2 years down the line.  Did you make a change to fix your change, how did your change scale for the future, did you change too much so that you simply ended up writing a whole new feature (which defeats the purpose of change nonetheless it is still technically a change).

I will be documenting on how I went about designing such a DSL in later Blogs.  Hopefully it will give me a better understanding on how to manage change outside the IT sector and apply it to more generic changes…such as company decisions and risk analysis.

Keep an eye out…