Some Creativity

Weblog of Siddharth Uppal

Archive for October, 2008

Automatic properties with custom logic – part 2

with 5 comments

Let’s say we have a class called ShoppingCartItem. We’re going to concern ourselves with two properties here namely TaxID and Taxable. The business logic dictates that an item is definitely non-taxable if it has a tax-id of zero, otherwise it may or may not be taxable which is decided by some code situated outside the ShoppingCartItem class.

How would you write your ShoppingCartItem class?

Now – we cannot really take advantage of automatic properties over here.


	public class ShoppingCartItem
	{
		public int TaxID { get; set; }
		public bool Taxable { get; set; }
	}

We really want the ShoppingCartItem to unset the Taxable flag when TaxID is zero. The setter for TaxID property seems to be the most obvious place to do that. But since we’re relying on the C# compiler to generate the getter/setter code we don’t really have control over that.

Of course, we have to fall back to the old way of doing it.


	public class ShoppingCartItem
	{
		private int mTaxID;

		public int TaxID
		{
			get
			{
				return this.mTaxID;
			}
			set
			{
				this.mTaxID = value;
				if (this.mTaxID == 0)
					this.Taxable = false;
			}
		}
		public bool Taxable { get; set; }
	}

That solves the problem we initially set out to solve but isn’t future proof. That’s because there’s nothing stopping a future programmer from adding new code in ShoppingCartItem class that directly saves values to mTaxID field instead of using the property setter. This could lead to Taxable being inconsistent with the value of TaxID. It would be better if we could somehow prevent direct setting of the backing field corresponding to the TaxID property even within the class.

Well, here’s an idea that leverages the lambda expression support in C# 3.0 to give us something that’s concise to write.


	public class ShoppingCartItem
	{
		public PropertyValue<int> TaxID;
		public PropertyValue<bool> Taxable;

		public ShoppingCartItem()
		{
			this.TaxID = new PropertyValue<int>()
			{
				OnSet = value =>
				{
					if (value == 0)
						this.Taxable.Value = false;
				}

			};
			this.Taxable = new PropertyValue<bool>();
		}
	}

PropertyValue is the new class that I wrote. The idea is that you provide the code that should be executed when the TaxID is set, when initializing TaxID.

Here’s the code for the new class.


	public class PropertyValue < T > : System.ComponentModel.INotifyPropertyChanged
	{
		#region Private fields

		private Action mOnGet;
		private Action mOnSet;
		private T mValue;

		#endregion

		#region Constructors

		public PropertyValue()
		{
			this.mValue = default(T);
		}

		public PropertyValue(T initialValue)
		{
			this.mValue = initialValue;
		}

		public PropertyValue(T initialValue, Action onGet, Action onSet)
		{
			this.mValue = initialValue;
			this.mOnGet = onGet;
			this.mOnSet = onSet;
		}

		#endregion

		#region Properties

		public Action OnGet
		{
			private get
			{
				return this.mOnGet;
			}
			set
			{
				if (this.mOnGet == null)
				{
					this.mOnGet = value;
				}
				else
				{
					throw new InvalidOperationException("Getter can only be assigned once.");
				}
			}
		}

		public Action OnSet
		{
			private get
			{
				return this.mOnSet;
			}
			set
			{
				if (this.mOnSet == null)
				{
					this.mOnSet = value;
				}
				else
				{
					throw new InvalidOperationException("Setter can only be assigned once.");
				}
			}
		}

		public T Value
		{
			get
			{
				if (this.mOnGet != null)
				{
					this.mOnGet(this.mValue);
				}
				return this.mValue;
			}
			set
			{
				this.mValue = value;
				if (this.mOnSet != null)
				{
					this.mOnSet(this.mValue);
				}
				if (this.PropertyChanged != null)
				{
					this.PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs("Value"));
				}
			}
		}

		#endregion 

		#region INotifyPropertyChanged Members

		public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;

		#endregion

		#region Overridden base class methods

		public override string ToString()
		{
			if (this.mValue != null)
			{
				return this.mValue.ToString();
			}
			else
			{
				return base.ToString();
			}
		}

		#endregion

	}

While it does appear to solve the issue, I’m not quite satisfied. So let me end this by pointing out the things I don’t like in the implementation and ask for ideas:

  • It is wordier compared to the automatic property support in C# and requires you to know about lambda expressions. Here’s how I think it should have been:

    
    	public class ShoppingCartItem
    	{
    		public int TaxID
    		{
    			get;
    			set
    			{
    				if (value == 0)
    					this.Taxable = false;
    			}
    		}
    		public bool Taxable { get; set; }
    }
    

    This doesn’t work in C# 3.0 and generates a compiler error “ShoppingCartItem.TaxID.get’ must declare a body because it is not marked abstract, extern, or partial”.

  • Ideally, I should have been able to write the following which seems more succinct.

    
    	public class ShoppingCartItem
    	{
    		public PropertyValue<bool> Taxable = new PropertyValue</bool><bool>();
    		public PropertyValue<int> TaxID = new PropertyValue</int><int>()
    		{
    			OnSet = value => {
    				if (value == 0)
    					Taxable = false;
    			}
    
    		};
    	}
    

    But I cannot, because it generates a compiler error “A field initializer cannot reference the non-static field, method, or property”. This requires me to do the same thing inside the constructor which introduces an extra step.

What do you think?

Written by Sid

October 29th, 2008 at 12:51 pm

Automatic properties with custom logic

with 2 comments

There should be a compiler supported way in C# to declare a property with custom getter/setter logic without having to explicitly declare a backing field. In the absence of such a feature, if you want to have custom getter/setter logic you have to ditch automatic properties, declare a backing field explicitly and then there’s nothing stopping a future programmer from assigning directly to the backing field and the custom getter/setter logic not getting executed probably leading to bugs.

Update:Check out part 2 where I take an example to bring the problem into perspective and propose a solution…

Written by Sid

October 24th, 2008 at 12:08 pm

Posted in General, Tech/Hacks

Tagged with , , ,