Automatic properties with custom logic – part 2

Posted on October 29, 2008

5


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 TaxID;
public PropertyValue Taxable;

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

};
this.Taxable = new PropertyValue();
}
}

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 Taxable = new PropertyValue();
    public PropertyValue TaxID = new PropertyValue()
    {
    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?

About these ads
Posted in: .NET, Tech/Hacks