Tải bản đầy đủ (.pdf) (69 trang)

Expert VB 2005 Business Objects Second Edition phần 5 pdf

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (634.02 KB, 69 trang )

Implementing a read-only sorted view of a collection is relatively straightforward, but
implementing a view that is bidirectionally updatable is quite complex. And that’s exactly what
SortedBindingList does.
Acting As a View
Let’s look at the simple things first. The original collection, as an ICollection, has a set of proper-
ties, such as
Count and SyncRoot, that are simply exposed by SortedBindingList. For instance, here’s
the
Count property:
Public ReadOnly Property Count() As Integer _
Implements System.Collections.ICollection.Count, _
System.Collections.Generic.ICollection(Of T).Count
Get
Return mList.Count
End Get
End Property
This technique is repeated for all the ICollection, IList, and IEnumerable properties. The not-
able exception to this is the default property, which is quite a bit more complex and is discussed later.
If the original collection implements
IBindingList, it has a broader set of properties. It might
be editable and it might not. It might allow adding of new items or not. All these capabilities are
exposed through its
IBindingList interface, and SortedBindingList merely assumes the same set-
tings. For instance, here’s the
AllowEdit property:
Public ReadOnly Property AllowEdit() As Boolean _
Implements System.ComponentModel.IBindingList.AllowEdit
Get
If mSupportsBinding Then
Return mBindingList.AllowEdit
Else


Return False
End If
End Get
End Property
Recall from the constructor that if the original collection doesn’t implement IBindingList, then
mSupportsBinding will be False. In that case, AllowEdit returns False because in-place editing isn’t
valid unless the original collection implements
IBindingList. This technique is repeated for all the
IBindingList properties.
Applying a Sort
The IBindingList interface allows a sort to be applied to a collection, either ascending or descend-
ing, based on a single pr
oper
ty. This is done through the
ApplySort() method.
ApplySort Method
SortedBindingList implements two overloads of ApplySort(), making it possible to apply a sort
based on the string name of the property, as well as by a
PropertyDescriptor as required by
IBindingList:
Public Sub ApplySort( _
ByVal propertyName As String, _
ByVal direction As System.ComponentModel.ListSortDirection)
CHAPTER 5 ■ COMPLETING THE FRAMEWORK 253
6315_c05_final.qxd 4/13/06 12:36 PM Page 253
mSortBy = Nothing
If Len(propertyName) > 0 Then
Dim itemType As Type = GetType(T)
For Each prop As PropertyDescriptor In _
TypeDescriptor.GetProperties(itemType)

If prop.Name = propertyName Then
mSortBy = prop
Exit For
End If
Next
End If
ApplySort(mSortBy, direction)
End Sub
Public Sub ApplySort( _
ByVal [property] As System.ComponentModel.PropertyDescriptor, _
ByVal direction As System.ComponentModel.ListSortDirection) _
Implements System.ComponentModel.IBindingList.ApplySort
mSortBy = [property]
mSortOrder = direction
DoSort()
End Sub
The first overload creates a PropertyDescriptor for the named property and calls the second
overload. The second overload will also be called directly by data binding. It sets the
mSortBy and
mSortOrder fields to indicate the sort parameters, and calls DoSort(). The reason these two instance
fields are used to store the parameters is that these values are also exposed by
Public properties
such as
SortDirection:
Public ReadOnly Property SortDirection() As _
System.ComponentModel.ListSortDirection _
Implements System.ComponentModel.IBindingList.SortDirection
Get
Return mSortOrder
End Get

End Property
The DoSort() method actually does the sorting by assembling the key values into a private
collection and then sorting those values. Associated with each key value is a reference to the corre-
sponding item in the original collection.
ListItem Class
Associating the value of the property by which to sort with a reference to the corresponding child
object in the original collection requires a key/value list, which in turn requires a key/value class.
The
ListItem class maintains a relationship between a key and a reference to the corresponding
child object.
The key value is the value of the property from the child object on which the collection is to be
sorted. For example, when sorting a collection of
Customer objects by their Name property, the key
value will be the contents of the
Name property from the corresponding child object.
R
ather than maintaining an actual object r
efer
ence,
ListItem maintains the inde
x
v
alue of the
child item in the original collection. This is referred to as the
base index:
CHAPTER 5 ■ COMPLETING THE FRAMEWORK254
6315_c05_final.qxd 4/13/06 12:36 PM Page 254
Private Class ListItem
Implements IComparable(Of ListItem)
Private mKey As Object

Private mBaseIndex As Integer
Public ReadOnly Property Key() As Object
Get
Return mKey
End Get
End Property
Public Property BaseIndex() As Integer
Get
Return mBaseIndex
End Get
Set(ByVal value As Integer)
mBaseIndex = value
End Set
End Property
Public Sub New(ByVal key As Object, ByVal baseIndex As Integer)
mKey = key
mBaseIndex = baseIndex
End Sub
Public Function CompareTo(ByVal other As ListItem) As Integer _
Implements System.IComparable(Of ListItem).CompareTo
Dim target As Object = other.Key
If TypeOf Key Is IComparable Then
Return DirectCast(Key, IComparable).CompareTo(target)
Else
If Key.Equals(target) Then
Return 0
Else
Return Key.ToString.CompareTo(target.ToString)
End If
End If

End Function
Public Overrides Function ToString() As String
Return Key.ToString
End Function
End Class
I
n addition to associating the pr
oper
ty v
alue to the base index of the child object in the original
collection,
ListItem implements IComparable(Of T).
This inter
face enables the .NET Framework to
sort a collection of
ListItem objects. This interface requires implementation of the CompareTo()
method, which is responsible for comparing one ListItem object to another.
Of course, it is the key value that is to be compared, so
CompareTo() simply compares the value
of its
Key pr
operty to the
Key pr
operty from the other
ListItem object. I
f the type of the key value
implements
IComparable, then the call simply delegates to that interface:
CHAPTER 5 ■ COMPLETING THE FRAMEWORK 255
6315_c05_final.qxd 4/13/06 12:36 PM Page 255

If TypeOf Key Is IComparable Then
Return DirectCast(Key, IComparable).CompareTo(target)
Otherwise things are a bit more complex. Obviously, any objects can be compared for equality,
so that part is straightforward:
I
f Key.Equals(target) Then
Return 0
However, if the type of the key value doesn’t implement IComparable, then there’s no easy way
t
o see if one is greater than the other. To overcome this problem, both values are converted to their
string representations, which are then compared to each other:
Return Key.ToString.CompareTo(target.ToString)
While this is not perfect, it is the best we can do. And really this is an extreme edge case since
most types are comparable, including strings, numeric types, and dates. Given that most properties
are of those types, this solution works well in almost every case.
DoSort Method
Given the ListItem class and the sorting capabilities of the .NET Framework, the DoSort() method
is not hard to implement:
Private Sub DoSort()
Dim index As Integer
mSortIndex.Clear()
If mSortBy Is Nothing Then
For Each obj As T In mList
mSortIndex.Add(New ListItem(obj, index))
index += 1
Next
Else
For Each obj As T In mList
mSortIndex.Add(New ListItem(mSortBy.GetValue(obj), index))
index += 1

Next
End If
mSortIndex.Sort()
mSorted = True
OnListChanged(New ListChangedEventArgs(ListChangedType.Reset, 0))
End Sub
I
f
mSortBy is Nothing (which is quite possible
, as it is optional), then each child object is sorted
as is. In other words, it is the value of the child object itself that determines the sort order, rather
than any specific property on the child object. In this case,
DoSort() loops through every item in the
original collection, creating a
ListItem object for which the key value is the child object itself and
the index is the location of the child object within the or
iginal collection:
For Each obj As T In mList
mSortIndex.Add(New ListItem(obj, index))
index += 1
Next
CHAPTER 5 ■ COMPLETING THE FRAMEWORK256
6315_c05_final.qxd 4/13/06 12:36 PM Page 256
This scenario is quite common when creating a sorted view against an array of type String or
Integer, since there’s no meaning in setting an mSortBy value for those types.
For more complex child objects, however, an
mSortBy value is typically supplied. In that case,
a bit of reflection is used to retrieve the specified property value from the child object. That property
value is then used as the key value for the
ListItem object:

F
or Each obj As T In mList
mSortIndex.Add(New ListItem(mSortBy.GetValue(obj), index))
index += 1
N
ext
Remember that mSortBy is a System.ComponentModel.PropertyDescriptor object corresponding
to the key property.
PropertyDescriptor provides a GetValue() method that retrieves the property
value from the specified child object.
Whether or not
mSortBy is Nothing, the end result is a list of ListItem objects in a generic
List(Of ListItem) collection named mSortIndex. The List(Of T) class provides a Sort() method
that sorts the items in the list. Since
ListItem implements IComparable(Of T), that interface is used
to order the sort, meaning that the items end up sorted based on the key property value in each
ListItem object.
Since sorting changes the order of items in the list, the view object’s
ListChanged event is raised
to tell data binding that the view collection has effectively been reset. Keep in mind that the original
collection is entirely unaffected by this process, and doesn’t raise any events due to the sort being
applied.
Viewing the Sorted Values
You may be wondering how descending sorts are handled, since the Sort() method of List(Of T)
performed an ascending sort in the DoSort() method. Ascending and descending sorts are handled
by the view object’s default property.
The
IList interface requires that a default property be implemented. To retrieve an item,
SortedBindingList must be able to cr
oss-r

eference from the sorted position of the item to the
original position of the item in the original collection. The
OriginalIndex() helper method per-
for
ms this cross-reference operation:
Private Function OriginalIndex(ByVal sortedIndex As Integer) As Integer
If mSortOrder = ListSortDirection.Ascending Then
Return mSortIndex.Item(sortedIndex).BaseIndex
Else
Return mSortIndex.Item(mSortIndex.Count - 1 - sortedIndex).BaseIndex
End If
End Function
The method checks to see whether the sort is ascending or descending. The supplied index
value is then cross-referenced into the
mSortIndex list to find the actual index of the child item in
the original collection. In the case of an ascending sort, a straight cross-reference from the position
in
mSortIndex to the or
iginal collection is used. And in the case of a descending sor
t, the cr
oss-
r
eference process merely starts at the bottom of
mSortIndex and wor
ks toward the top.
The default property simply uses this helper method to retrieve or set the object from the
original collection that corresponds to the location in the sorted index:
Default Public Overloads Property Item(ByVal index As Integer) As T _
Implements System.Collections.Generic.IList(Of T).Item
Get

If mSorted Then
Return mList(OriginalIndex(index))
CHAPTER 5 ■ COMPLETING THE FRAMEWORK 257
6315_c05_final.qxd 4/13/06 12:36 PM Page 257
Else
Return mList(index)
End If
End Get
Set(ByVal value As T)
If mSorted Then
mList(OriginalIndex(index)) = value
Else
mList(index) = value
End If
End Set
End Property
Notice that the child object is ultimately returned from the original collection. The data in
SortedBindingList is merely used to provide a sorted cross-reference to those objects.
In the case that a sort hasn’t been applied at all, no cross-reference is performed and the child
object is returned from the original collection based directly on the index value:
Return mList(index)
The same technique is used in the Set block as well. Additionally, the IList interface requires
implementation of a loosely typed
Item proper
ty:
Private Property Item1(ByVal index As Integer) As Object _
Implements System.Collections.IList.Item
Get
Return Me(index)
End Get

Set(ByVal value As Object)
Me(index) = CType(value, T)
End Set
End Property
This property delegates its work to the strongly typed default property implemented previously.
Collection Enumerator
There are two ways to get items from a collection: the default proper
ty and an enumerator. The
enumerator is used by the
For Each statement to loop through all items in the collection. Obvi-
ously, it too needs to perform a cross-reference process, so a
For Each loop goes through the
sorted index and returns the corresponding item from the original collection.
There are two steps to this process. First, the custom enumerator class must understand how
to per
for
m the cr
oss-r
eference process. Second,
SortedBindingList needs to expose a
GetEnumerator() method that r
eturns an instance of this custom enumerator (or the original
collection’s enumerator if no sort has been applied).
Custom Enumerator Class
An enumer
ator
is an object that implements either
IEnumerator or IEnumerator(Of T).
These
inter

faces define a
Current pr
operty and
MoveNext() and Reset() methods
. You can think of an
enumerator object as being a cursor or pointer into the collection. Table 5-2 describes these
elements.
CHAPTER 5 ■ COMPLETING THE FRAMEWORK258
6315_c05_final.qxd 4/13/06 12:36 PM Page 258
Table 5-2. Properties and Methods of an Enumerator Object
Member Behavior
Current Returns a reference to the current child object in the collection
MoveNext() Moves to the next child object in the collection, making that the current object
Reset() Moves to just above the top of the collection, so a subsequent MoveNext() call
moves to the very first item in the collection
When you use a
For Each statement in your code, the compiler generates code behind the
scenes to get an enumerator object from the collection, and to call the
Reset(), MoveNext(), and
Current elements to iterate through the items in the collection.
Because an enumerator object is a cursor or pointer into the collection, it must maintain a cur-
rent index position. The
SortedEnumerator class used by SortedBindingList also needs to know the
sort order and must have access to the original collection itself:
Private Class SortedEnumerator
Implements IEnumerator(Of T)
Private mList As IList(Of T)
Private mSortIndex As List(Of ListItem)
Private mSortOrder As ListSortDirection
Private mIndex As Integer

Public Sub New( _
ByVal list As IList(Of T), _
ByVal sortIndex As List(Of ListItem), _
ByVal direction As ListSortDirection)
mList = list
mSortIndex = sortIndex
mSortOrder = direction
Reset()
End Sub
The constructor accepts a reference to the original collection, a reference to the mSortIndex list
containing the sorted list of
ListItem objects, and the sort direction. The mIndex field is used to
maintain a pointer to the current position of the enumerator within the collection.
The
Reset() method simply sets index to immediately before the first item in the collection.
Of course, when using a descending sort, this is actually immediately
after the last item in the col-
lection, because the enumerator will walk through the list from bottom to top in that case:
Public Sub Reset() Implements System.Collections.IEnumerator.Reset
If mSortOrder = ListSortDirection.Ascending Then
mIndex = -1
Else
mIndex = mSortIndex.Count
End If
End Sub
The MoveNext() method
increments
mIndex, mo
ving to the next item in the collection. Again,
when using a descending sort, it actually decrements

mIndex, thus moving from the bottom of the
collection toward the top.
CHAPTER 5 ■ COMPLETING THE FRAMEWORK 259
6315_c05_final.qxd 4/13/06 12:36 PM Page 259
Public Function MoveNext() As Boolean _
Implements System.Collections.IEnumerator.MoveNext
If mSortOrder = ListSortDirection.Ascending Then
If mIndex < mSortIndex.Count - 1 Then
mIndex += 1
Return True
Else
Return False
End If
Else
If mIndex > 0 Then
mIndex -= 1
Return True
Else
Return False
End If
End If
End Function
The MoveNext() method returns a Boolean value, returning False when there are no more items
in the collection. In other words, when it reaches the bottom of the list (or the top when doing a
descending sor
t), it r
eturns
False to indicate that ther
e ar
e no more items.

The
Current property simply returns a reference to the child object corresponding to the current
value of
mIndex. Of course, mIndex is pointing to an item in the sorted list, and so that value must be
cross-referenced back to an item in the original collection. This is the same as in the default pr
operty
earlier:
Public ReadOnly Property Current() As T _
Implements System.Collections.Generic.IEnumerator(Of T).Current
Get
Return mList(mSortIndex(mIndex).BaseIndex)
End Get
End Property
Private ReadOnly Property CurrentItem() As Object _
Implements System.Collections.IEnumerator.Current
Get
Return mList(mSortIndex(mIndex).BaseIndex)
End Get
End Property
B
ecause
SortedEnumerator implements IEnumerator(Of T), it actually has two Current pr
oper-
ties—one strongly typed for
IEnumerator(Of T) itself, and the other loosely typed for IEnumerator
(from which IEnumerator(Of T) inherits).
Both do the same thing, using the
mIndex value to find the appropriate ListItem object in the
sor
ted list, and then using the

BaseIndex pr
operty of
ListItem to r
etrieve the corresponding item
in the original collection. That child object is then returned as a result.
GetEnumerator Method
Collection objects must implement a GetEnumerator() method. This is required by the IEnumerable
interface, which is the most basic interface for collection or list objects in the .NET Framework. In
CHAPTER 5 ■ COMPLETING THE FRAMEWORK260
6315_c05_final.qxd 4/13/06 12:36 PM Page 260
the case of SortedBindingList, both strongly typed and loosely typed GetEnumerator() methods
must be implemented:
Public Function GetEnumerator() As _
System.Collections.Generic.IEnumerator(Of T) _
Implements System.Collections.Generic.IEnumerable(Of T).GetEnumerator
I
f mSorted Then
Return New SortedEnumerator(mList, mSortIndex, mSortOrder)
Else
Return mList.GetEnumerator
End If
End Function
Private Function GetItemEnumerator() As System.Collections.IEnumerator _
Implements System.Collections.IEnumerable.GetEnumerator
Return GetEnumerator()
End Function
These methods merely return an instance of an enumerator object for use by For Each
statements that wish to iterate through the items in the collection.
If the view is not currently sorted, then it can simply ask the original collection for
its enumer-

ator. The original collection’s enumerator will already iterate through all the child objects in the
collection in their original order:
Return mList.GetEnumerator
On the other hand, if a sort has been applied, then an instance of the custom SortedEnumerator
(implemented in the preceding code) is returned:
Return New SortedEnumerator(mList, mSortIndex, mSortOrder)
Either way, the compiler-generated code for the For Each statement has an enumerator
object that iterates through the items in the collection.
Remo
ving the Sort
The IBindingList interface allows for removal of the sort. The result should be that the items in the
collection return to their original order. This is handled by an
UndoSort() method:
Private Sub UndoSort()
mSortIndex.Clear()
mSortBy = Nothing
mSortOrder = ListSortDirection.Ascending
mSorted = False
OnListChanged(New ListChangedEventArgs(ListChangedType.Reset, 0))
End Sub
Removing a sort is just a matter of setting mSorted to False and clearing the various sort-related
fields. Most important is calling
Clear() on mSortIndex, as that releases any possible object refer-
ences to items in the or
iginal collection.
Because removing the sort alters the order of items in the view, the
ListChanged event is raised
to tell the UI that it needs to refresh its display of the collection.
CHAPTER 5 ■ COMPLETING THE FRAMEWORK 261
6315_c05_final.qxd 4/13/06 12:36 PM Page 261

Adding and Removing Items
Now we get to the complex issues. Remember that SortedBindingList is an updatable view of the
original collection. This means that when the user adds or removes an item from the original collec-
tion, that change is immediately reflected in the view; the view is even re-sorted, if appropriate.
Conversely, if the user adds or removes an item from the view, that change is immediately reflected
in the original collection. There’s some work involved in keeping the view and collection in sync.
Also remember that collections may raise
ListChanged events as they are changed. Table 5-3
l
ists the add and remove operations and how they raise events.
Table 5-3. Events Raised During Add and Remove Operations
Operation Event Behavior
AddNew() Called by data binding to add an item to the end of the collection; an ItemAdded type
ListChanged event is raised by the collection
Insert() Inserts an item into the collection; an ItemAdded type ListChanged event is raised by
the collection
RemoveAt() Removes an item from the collection; an ItemDeleted type ListChanged event is
raised by the collection
A
ListChanged event is raised when the user adds or removes an item from the original collection.
This event must be handled and sometimes reraised by the view. This is illustrated in Figure 5-2.
F
igur
e 5-2 sho
ws the simple case
, in which both the or
iginal collection and the view ar
e bound
to separate controls on the UI, and an update to the original collection is made.
However, when the user adds or removes an item through the view, the view raises a

ListChanged
ev
ent as w
ell as updating the original collection. Of course, updating the original collection triggers
its
ListChanged event. If you’re not careful, this could result in duplicate events being raised, as shown in
Figure 5-3.
CHAPTER 5 ■ COMPLETING THE FRAMEWORK262
Figure 5-2. Flow of events when the user changes the original collection
6315_c05_final.qxd 4/13/06 12:36 PM Page 262
In this case, the UI control bound to the sorted view gets the ListChanged event twice, which is
wasteful. But when the change is applied to the original collection, its event could flow back to the
view and then to the UI.
Figure 5-4 shows what
should happen when the user changes the view
.
CHAPTER 5 ■ COMPLETING THE FRAMEWORK 263
Figure 5-3. Duplicate events raised when the user changes the view
Figure 5-4. Flow of events with no duplicate events being raised
6315_c05_final.qxd 4/13/06 12:36 PM Page 263
Making this happen means keeping track of whether the user added or removed an object
directly in the original collection or through the view. The view needs to know whether the change
was initiated locally, on the view, or not. This is tracked by the
mInitiatedLocally field, which is set
to
True before SortedBindingList performs any add or remove operations on the original collection,
and is set to
False when it is done.
Adding and removing items to and from the view is done through the
AddNew(), Insert(), and

RemoveAt() methods. AddNew() and RemoveAt() are handled in a similar manner:
Public Function AddNew() As Object _
Implements System.ComponentModel.IBindingList.AddNew
Dim result As Object
If mSupportsBinding Then
mInitiatedLocally = True
result = mBindingList.AddNew
mInitiatedLocally = False
OnListChanged(New ListChangedEventArgs( _
ListChangedType.ItemAdded, mBindingList.Count - 1))
Else
result = Nothing
End If
Return result
End Function
Public Sub RemoveAt(ByVal index As Integer) _
Implements System.Collections.IList.RemoveAt, _
System.Collections.Generic.IList(Of T).RemoveAt
If mSorted Then
mInitiatedLocally = True
Dim baseIndex As Integer = OriginalIndex(index)
' remove the item from the source list
mList.RemoveAt(baseIndex)
' delete the corresponding value in the sort index
mSortIndex.RemoveAt(index)
' now fix up all index pointers in the sort index
For Each item As ListItem In mSortIndex
If item.BaseIndex > baseIndex Then
item.BaseIndex -= 1
End If

Next
OnListChanged( _
New ListChangedEventArgs(ListChangedType.ItemDeleted, index))
mInitiatedLocally = False
Else
mList.RemoveAt(index)
End If
End Sub
Remember that mBindingList is a reference to the original collection object’s implementation
of the
IBindingList interface. So this code merely sets mInitiatedLocally to True and then dele-
gates the
AddNew() call to
the or
iginal collection. S
imilarly, the
RemoveAt() call is delegated to the
original collection through its
IList(Of T) interface.
CHAPTER 5 ■ COMPLETING THE FRAMEWORK264
6315_c05_final.qxd 4/13/06 12:36 PM Page 264
I’ve also optimized the RemoveAt() implementation, so when an item is removed from the
middle of the list, the entire sorted index isn’t rebuilt. This offers substantial performance improve-
ments when dealing with larger-sized lists.
■Note The important thing here is that SortedBindingList doesn’t maintain a local copy of the collection’s
data. Instead, it delegates all calls directly to the original collection itself.
The original collection performs the requested operation and adds or removes the child object.
Of course, that triggers a
ListChanged event from the original collection. Recall that in the construc-
tor of

SortedBindingList, the original collection’s ListChanged event was handled by the
SourceChanged() method. I’ll cover the SourceChanged() method in a moment, and you’ll see how the
ListChanged event is suppressed when the add or remove operation is initiated by the view itself.
The
Insert() method is simpler:
Public Sub Insert(ByVal index As Integer, ByVal item As T) _
Implements System.Collections.Generic.IList(Of T).Insert
mList.Insert(index, item)
End Sub
Private Sub Insert(ByVal index As Integer, ByVal value As Object) _
Implements System.Collections.IList.Insert
Insert(index, CType(value, T))
End Sub
When a new item is inserted into the view, it is really inserted into the original collection. This
results in the original collection raising its
ListChanged event, and in turn the view then raises its
ListChanged event (in the SourceChanged() method in the following code). The end result is that the
view raises the
ListChanged event exactly once, which is the desired goal.
Responding to Changed Data
The source collection’s ListChanged event is handled by the SourceChanged() method. This allows
SortedBindingList to re-sort the data if it is changed in the original collection, thus keeping the
view current. It also means that the event can be reraised by the view so that any UI components
bound to the sorted view are also aware that the underlying data has changed.
If no sort has been applied, then the only thing the
SourceChanged() method needs to do is
r
er
aise the ev
ent:

Private Sub SourceChanged( _
ByVal sender As Object, ByVal e As ListChangedEventArgs)
If mSorted Then
Select Case e.ListChangedType
' update sorted view based on type of change
End Select
Else
OnListChanged(e)
End If
End Sub
CHAPTER 5 ■ COMPLETING THE FRAMEWORK 265
6315_c05_final.qxd 4/13/06 12:36 PM Page 265
The OnListChanged() method raises the ListChanged event from the SortedBindingList object.
Notice that the exact same event is raised, so in this case the UI is blissfully unaware that
SortedBindingList is a view over the original collection.
However, if the view is sorted, then things are far more complex. In this case, the view must be
updated appropriately based on the underlying change in the original collection. The
ListChanged
event can indicate different types of changes, each of which must be handled in a different manner.
T
he code that goes in the preceding
S
elect
b
lock takes care of these details. Let’s go through each
Case of that block.
Adding a New Item
If a new item was added to the original collection, then the sorted view must also add a new
ListItem object to the sorted index. It is possible for an item to be added in the middle or at the end
of the original collection.

If the item was added at the end of the original collection, the new
ListItem object needs to
contain the new child object’s key property value and the index location of the item in the original
collection.
But if the item was inserted in the middle of the original collection, then all the cross-reference
indexes in the sort index become potentially invalid. The simplest way to ensure that they are all
correct is to call
DoSort() and rebuild the sort index completely:
Case ListChangedType.ItemAdded
Dim newItem As T = mList(e.NewIndex)
If e.NewIndex = mList.Count - 1 Then
Dim newKey As Object
If mSortBy IsNot Nothing Then
newKey = mSortBy.GetValue(newItem)
Else
newKey = newItem
End If
If mSortOrder = ListSortDirection.Ascending Then
mSortIndex.Add(New ListItem(newKey, e.NewIndex))
Else
mSortIndex.Insert(0, New ListItem(newKey, e.NewIndex))
End If
If Not mInitiatedLocally Then
OnListChanged( _
New ListChangedEventArgs( _
ListChangedType.ItemAdded, SortedIndex(e.NewIndex)))
End If
Else
DoSort()
End If

The har
d wor
k occurs if the new item was added to the end of the or
iginal collection. I
n that
case
, the item’s key property value is retrieved based on the value of
mSortBy; just like in the
DoSort() method.
Then a new
ListItem object is created and inserted into the list—at the end for an ascending
sort, and at the beginning for a descending sort. This ensures that the new item appears at the very
bottom of a gr
id or list control when the sorted view is a data source for such a UI control.
Finally, if the addition of the new item was not initiated locally, then a
ListChanged event is
raised to tell the UI about the new item. This is important, because if the new item
was added
CHAPTER 5 ■ COMPLETING THE FRAMEWORK266
6315_c05_final.qxd 4/13/06 12:36 PM Page 266
locally to the view, then no ListChanged event should be raised at this point; instead, the event is
raised by the local
AddNew() method itself.
Removing an Item
W
hen an item is removed from the original collection, a
L
istChanged
e
vent is raised.

SortedBindingList handles this event. If the removal was initiated by the original collection,
then the view is simply re-sorted:
C
ase ListChangedType.ItemDeleted
If Not mInitiatedLocally Then
DoSort()
End If
This is the easiest approach, since it causes automatic removal of the ListItem object corre-
sponding to the removed item, and recalculation of all the cross-reference index values between
the sorted list and the original collection.
Notice that if the removal was initiated by the view itself, then the view isn’t re-sorted. This is
because the
RemoveAt() method in SortedBindingList removes both the original item and the cor-
responding
ListItem object, and recalculates all the cross-reference index values.
By using a combination of delegation to the original collection and implementation of a
cross-r
eference scheme between the sorted list and the original list,
SortedBindingList provides
a bidirectionally updatable, sorted view of any
IList(Of T) array or collection.
Date Handling
One
common view of good UI design holds that the user should be free to enter arbitrary text,
and it is up to the application to make sense of the entry. Nowhere is this truer than with date
values, and the
SmartDate type is designed to simplify ho
w a business developer uses dates and
exposes them to the UI.
Examples of free-form date entry are easy to find. Just look at widely used applications like

Microsoft Money or Intuit
’s Quicken. In these applications, users are free to enter dates in whatever
format is easiest for them. Additionally, various shortcuts are supported; for example, the
+ charac-
ter means tomorrow, while
– means yesterday.
Most users find this approach more appealing than being forced to enter a date in a strict for-
mat through a masked edit control, or having to always use the mouse to use a graphical calendar
contr
ol. Of course, being able to
additionally suppor
t a calendar control is also a great UI design
choice.
Date handling is also quite challenging because the standard
DateTime data type doesn’t have
any comprehension of an “empty” or “blank” date.
Many applications have date values that may be empty for a time and are filled in later. Con-
sider a sales or
der
, in which the shipment date is unknown when the order is entered. That date
should remain blank or empty until an actual date
is known. Without having the concept of an
empty date, an application will require the user to enter an invalid “placeholder” date until the
real date is known; and that’s just poor application design.
■Tip In the early 1990s, I worked at a company where all “far-future” dates were entered as 12/31/99. Guess
how much trouble the company had around Y2K, when all of its never-to-be-delivered orders started coming due!
CHAPTER 5 ■ COMPLETING THE FRAMEWORK 267
6315_c05_final.qxd 4/13/06 12:36 PM Page 267
It is true that the Nullable(Of T) type can be applied to a DateTime value like this:
Nullable(Of DateTime). This allows a date to be “empty” in a limited sense. Unfortunately, that

isn’t enough for many applications, since an actual date value can’t be meaningfully compared to
Nothing. Is Nothing greater than or less than a given date? With Nullable(Of T), the answer is an
exception; which is not a very useful answer.
Additionally, data binding doesn’t deal well with
Nothing values, and so exposing a Nothing
v
alue from a business object’s property often complicates the UI code.
SmartDate
The Csla.SmartDate type is designed to augment the standard .NET DateTime type to make it easier
to work with date values. In particular, it provides the following key features:
• Automatic translation between
String and DateTime types
• Translation of shortcut values to valid dates
• Understanding of the concept of an “empty” date
• Meaningful comparison between a date and an empty date
• Backward compatibility with
SmartDate from the previous edition of this book
The
DateTime data type is marked NotInheritable, meaning that a new type can’t inher
it
from it to create a different data type. However, it is possible to use containment and delegation
to “wrap” a
DateTime value with extra functionality. That’s exactly how the SmartDate type is
implemented. Like
DateTime itself, SmartDate is a value type:
<Serializable()> _
Public Structure SmartDate
Implements IComparable
Private mDate As Date
Private mEmptyIsMax As Boolean

Private mFormat As String
Private mInitialized As Boolean
Notice that it has an mDate instance field, which is the underlying DateTime value of the
SmartDate.
Supporting empty date values is more complex than it might appear. An empty date still has
meaning, and in fact it is possible to compare a regular date to an empty date and get a valid result.
C
onsider the pr
evious sales or
der example
. If the shipment date is unknown, it will be empty.
But effectively, that empty date is infinitely far in the future. Were you to compare that empty ship-
ment date to any other date, the shipment date would be the larger of the two.
Conversely, there are cases in which an empty date should be considered to be smaller than
the smallest possible date
.
This concept is impor
tant, as it allows for meaningful comparisons between dates and
empty dates. Such comparisons make implementation of validation rules and other business
logic far simpler. You can, for instance, loop through a set of
Order objects to find all the objects
with a shipment date less than today; without the need to worry about empty dates:
For Each order As Order In OrderList
If order.ShipmentDate <= Today Then
Assuming ShipmentDate is a SmartDate, this will work great, and any empty dates will be
considered to be larger than any actual date value.
CHAPTER 5 ■ COMPLETING THE FRAMEWORK268
6315_c05_final.qxd 4/13/06 12:36 PM Page 268
The mEmptyIsMax field keeps track of whether the SmartDate instance should consider an empty
date to be the smallest or largest possible date value. If it is

True, then an empty date is considered
to be the largest possible value.
The
mFormat field stores a .NET format string that provides the default format for converting
a
DateTime value into a string representation.
The
mInitialized field keeps track of whether the SmartDate has been initialized. Remember
t
hat
S
martDate
i
s a
S
tructure
,
not an object. This severely restricts how the type’s fields can be
initialized.
Initializing the Structure
As with any Structure, SmartDate can be created with or without calling a constructor. This means
a business object could declare
SmartDate fields using any of the following:
Private mDate1 As SmartDate
Private mDate2 As New SmartDate(False)
Private mDate3 As New SmartDate(Today)
Private mDate4 As New SmartDate(Today, True)
Private mDate5 As New SmartDate("1/1/2005", True)
Private mDate6 As New SmartDate("", True)
In the first two cases

, the
SmartDate will star
t out being empty, with empty meaning that it has
a value
smaller than any other date.
The
mDate3 value will start out containing the current date. It if is set to an empty value later,
that empty value will correspond to a value
smaller than any other date.
The next two values are initialized either to the current date or a fixed date based on a
String
value. In both cases, if the SmartDate is set to an empty value later, that empty value will correspond
to a value
larger than any other date.
Finally,
mDate6 is initialized to an empty date value, where that value is larger than any other
date.
Handling this initialization is a bit tricky, since a
Structure can’t have a default constructor. Yet
even in the case of
mDate1, some initialization is required. This is the purpose of the mInitialized
instance field. It, of course, defaults to a value of False, and so can be used in the properties of the
Structure to determine whether the Structure has been initialized. As you’ll see, this allows
SmartDate to initialize itself the first time a property is called; assuming it hasn’t been initialized
previously.
All the constructors follow the same basic flow. Here’s one of them:
Public Sub New(ByVal value As String, ByVal emptyIsMin As Boolean)
mEmptyIsMax = Not emptyIsMin
Me.Text = value
mInitialized = True

End Sub
In this constructor, the Text property is used to set the date value based on the value param-
eter passed into the constr
uctor
.
This includes tr
anslation of an empty
String v
alue into the
appr
opriate empty date value.
Also look at the
emptyIsMin parameter. Remember that SmartDate actually maintains an
mEmptyIsMax field—the exact opposite of the parameter’s meaning. This is why the parameter
value is negated as
mEmptyIsMax is assigned. This is a bit awkward, but necessary for preserving
backwar
d compatibility with the
SmartDate type fr
om the previous edition of this book, and thus
previous versions of CSLA .NET.
CHAPTER 5 ■ COMPLETING THE FRAMEWORK 269
6315_c05_final.qxd 4/13/06 12:36 PM Page 269
■Note This highlights a key design consideration for frameworks in general. Backward compatibility is a key
feature of frameworks, since breaking compatibility means going through every bit of code based on the frame-
w
ork to adjust to the change. While sometimes awkward, it is often worth adding extra code to a framework in
order to preserve backward compatibility.
The reason the field is the reverse of the property is that the default value for a SmartDate is
that

EmptyIsMin is True. Given that you can’t initialize fields in a Structure, it is simpler to accept
the default value for a Boolean, which is
False. Hence the use of mEmptyIsMax as a field, since if it
is
False (the default), then EmptyIsMin is True by default.
Supporting Empty Dates
SmartDate already has a field to control whether an empty date represents the largest or smallest
possible date. This field is exposed as a property so that other code can determine how dates are
handled:
Public ReadOnly Property EmptyIsMin() As Boolean
Get
Return Not mEmptyIsMax
End Get
End Property
SmartDate also implements an IsEmpty property so that code can ask if the SmartDate object
represents an empty date:
Public ReadOnly Property IsEmpty() As Boolean
Get
If Not mEmptyIsMax Then
Return Me.Date.Equals(Date.MinValue)
Else
Return Me.Date.Equals(Date.MaxValue)
End If
End Get
End Property
Notice the use of the mEmptyIsMax flag to determine whether an empty date is to be considered
the largest or smallest possible date for comparison purposes. If it is the smallest date, then it is
empty if the date v
alue equals
DateTime.MinValue; if it is the lar

gest date
, it is empty if the v
alue
equals
DateTime.MaxValue.
Conversion Functions
G
iv
en
this understanding of empty dates
, it is possible to cr
eate a couple of functions to conv
ert
dates to text (or text to dates) intelligently. For consistency with other .NET types,
SmartDate will
also include a
Parse() method to convert a String into a SmartDate. These will be Shared methods
so that they can be used ev
en without creating an instance of
SmartDate. U
sing these methods, a
developer can write business logic such as this:
Dim userDate As DateTime = SmartDate.StringToDate(userDateString)
Table 5-4 shows the results of this function, based on various user text inputs.
CHAPTER 5 ■ COMPLETING THE FRAMEWORK270
6315_c05_final.qxd 4/13/06 12:36 PM Page 270
Table 5-4. Results of the StringToDate Method Based on Various Inputs
User Text Input EmptyIsMin Result of StringToDate()
String.Empty True (default) DateTime.MinValue
String.Empty False DateTime.MaxValue

Any text that can be parsed as a date True or False (ignored) A date value
StringToDate() converts a string value containing a date into a DateTime value. It understands
that an empty
String should be converted to either the smallest or the largest date, based on an
optional parameter.
It also handles translation of shortcut values to valid date values. The characters
., +, and –
correspond to today, tomorrow, and yesterday, respectively. Additionally, the values t, today, tom,
tomorrow, y, and yesterday work in a similar manner. These text values are defined in the project’s
Resource.resx file, and so are subject to localization for other languages.
Her
e’s the code:
Public Shared Function StringToDate(ByVal value As String) As Date
Return StringToDate(value, True)
End Function
Public Shared Function StringToDate( _
ByVal value As String, ByVal emptyIsMin As Boolean) As Date
If Len(value) = 0 Then
If emptyIsMin Then
Return Date.MinValue
Else
Return Date.MaxValue
End If
ElseIf IsDate(value) Then
Return CDate(value)
Else
Select Case LCase(Trim(value))
Case My.Resources.SmartDateT, My.Resources.SmartDateToday, "."
Return Now
Case My.Resources.SmartDateY, My.Resources.SmartDateYesterday, "-"

Return DateAdd(DateInterval.Day, -1, Now)
Case My.Resources.SmartDateTom, My.Resources.SmartDateTomorrow, "+"
Return DateAdd(DateInterval.Day, 1, Now)
Case Else
Throw New ArgumentException(My.Resources.StringToDateException)
End Select
End If
End Function
G
iv
en a
String of nonz
er
o length, this
function attempts to parse it dir
ectly to a
DateTime field.
If that fails, then the various shortcut values are checked. If that fails as well, then an exception is
thrown to indicate that the
String value couldn’t be parsed into a date.
CHAPTER 5 ■ COMPLETING THE FRAMEWORK 271
6315_c05_final.qxd 4/13/06 12:36 PM Page 271
SmartDate can translate dates the other way as well, such as converting a DateTime field into a
String and retaining the concept of an empty date. Again, an optional parameter controls whether
an empty date represents the smallest or the largest possible date. Another parameter controls the
format of the date as it’s converted to a
String. Table 5-5 illustrates the results for various inputs.
Table 5-5. Results of the DateToString Method Based on Various Inputs
User Date Input EmptyIsMin Result of DateToString()
DateTime.MinValue True (default) String.Empty

DateTime.MinValue False DateTime.MinValue
DateTime.MaxValue True
(default) DateTime.MaxValue
DateTime.MaxValue False String.Empty
Any other valid date True or False (ignored) String representing the date value
Add the following code to the same region:
Public Shared Function DateToString( _
ByVal value As Date, ByVal formatString As String) As String
Return DateToString(value, formatString, True)
End Function
Public Shared Function DateToString( _
ByVal value As Date, ByVal formatString As String, _
ByVal emptyIsMin As Boolean) As String
If emptyIsMin AndAlso value = Date.MinValue Then
Return ""
ElseIf Not emptyIsMin AndAlso value = Date.MaxValue Then
Return ""
Else
Return String.Format("{0:" + formatString + "}", value)
End If
End Function
This functions as a mirror to the StringToDate() method. This means it is possible to start
with an empty
String, convert it to a DateTime, and then convert that DateTime back into an empty
String.
Notice that this method requires a format string, which defines how the
DateTime value is
to be formatted as a
String. This is used to create a complete .NET format string such as {0:d}.
Finally, there’s the

Parse() method, which accepts a String value and returns a SmartDate.
There are two variations on this method:
Public Shared Function Parse(ByVal value As String) As SmartDate
Return New SmartDate(value)
End Function
Public Shared Function Parse( _
ByVal value As String, ByVal emptyIsMin As Boolean) As SmartDate
Return New SmartDate(value, emptyIsMin)
End Function
The first uses the default True value for EmptyIsMin, while the second allows the caller to specify
the value. Neither is hard to implement given the constructors already present in the code.
CHAPTER 5 ■ COMPLETING THE FRAMEWORK272
6315_c05_final.qxd 4/13/06 12:36 PM Page 272
Text Functions
Next, let’s implement functions in SmartDate that support both text and DateTime access to the
underlying
DateTime value. When business code wants to expose a date value to the UI, it will often
want to expose it as a
String. (Exposing it as a DateTime precludes the possibility of the user enter-
ing a blank value for an empty date, and while that’s great if the date is required, it isn’t good for
optional date values.)
Exposing a date as text requires the ability to format the date properly. To make this manage-
a
ble, the
m
Format
f
ield is used to control the format used for outputting a date.
S
martDate

i
ncludes
a property so that the business developer can alter this format value to override the default:
Public Property FormatString() As String
Get
If mFormat Is Nothing Then
mFormat = "d"
End If
Return mFormat
End Get
Set(ByVal value As String)
mFormat = value
End Set
End Property
The default value is d for the short date format. This is handled in the Get block, which is
important given that the
mFormat field will default to a value of Nothing unless explicitly set to
something else.
Given the
FormatString property
, the
Text property can use the StringToDate() and
DateToString() methods to translate between text and date values. This property can be used
to retrieve or set values using
String representations of dates, where an empty String is appro-
priately handled:
Public Property Text() As String
Get
Return DateToString(Me.Date, FormatString, Not mEmptyIsMax)
End Get

Set(ByVal value As String)
Me.Date = StringToDate(value, Not mEmptyIsMax)
End Set
End Property
This property is used in the constructors as well, meaning that the same rules for dealing with
an empty date apply during object initialization, as when setting its value via the
Text property.
There’s one other text-oriented method to implement:
ToString(). All objects in .NET have
a
ToString() method, which ideally r
eturns a useful text representation of the object’s contents.
In this case, it should return the formatted date value:
Public Overrides Function ToString() As String
Return Me.Text
End Function
S
ince the
Text pr
oper
ty alr
eady converts the
SmartDate v
alue to a
String, this is easy to
implement.
Date Functions
It should be
possible to tr
eat a

SmartDate like a r
egular
DateTime—as much as possible
, anyway
.
Since it’s not possible for it to inherit from
DateTime, there’s no way for it to be treated just like a
CHAPTER 5 ■ COMPLETING THE FRAMEWORK 273
6315_c05_final.qxd 4/13/06 12:36 PM Page 273
regular DateTime. The best approximation is to implement a Date property that returns the internal
value:
Public Property [Date]() As Date
Get
If Not mInitialized Then
mDate = Date.MinValue
m
Initialized = True
End If
Return mDate
End Get
Set(ByVal value As Date)
mDate = value
mInitialized = True
End Set
End Property
Notice the use of the mInitialized field to determine whether the SmartDate has been initial-
ized. If the
SmartDate instance was declared without explicitly calling one of the constructors, then
it will not have been initialized, so the
mDate field needs to be set before it can be returned. It is set

to
DateTime.MinValue because that is the empty date when mEmptyIsMax is False (which it is by
default).
IComparable
SmartDate implements the IComparable interface, which defines a CompareTo() method. The
CompareTo() method is used by the .NET Framework in various ways, most notably to support
sorting within sorted collections and lists. This
CompareTo() method is overloaded to also include
a strongly typed
CompareTo() that directly accepts a SmartDate:
Public Function CompareTo(ByVal obj As Object) As Integer _
Implements IComparable.CompareTo
If TypeOf obj Is SmartDate Then
Return CompareTo(DirectCast(obj, SmartDate))
Else
Throw New ArgumentException(My.Resources.ValueNotSmartDateException)
End If
End Function
Public Function CompareTo(ByVal value As SmartDate) As Integer
If Me.IsEmpty AndAlso value.IsEmpty Then
Return 0
Else
Return Me.Date.CompareTo(value.Date)
End If
End Function
Because empty dates are maintained as DateTime.MinValue or DateTime.MaxValue, they will
automatically sort to the top or bottom of the list based on the setting of
mEmptyIsMax. For ease of
use
,

SmartDate also includes similar CompareTo() o
verloads that accept
String and DateTime.
Date Manipulation
SmartDate should provide arithmetic manipulation of the date value. Since the goal is to emulate a
regular
DateTime data type, it should provide at least Add() and Subtract() methods:
CHAPTER 5 ■ COMPLETING THE FRAMEWORK274
6315_c05_final.qxd 4/13/06 12:36 PM Page 274
Public Function Add(ByVal value As TimeSpan) As Date
If IsEmpty Then
Return Me.Date
Else
Return Me.Date.Add(value)
End If
End Function
Public Function Subtract(ByVal value As TimeSpan) As Date
If IsEmpty Then
Return Me.Date
Else
Return Me.Date.Subtract(value)
End If
End Function
Public Function Subtract(ByVal value As Date) As TimeSpan
If IsEmpty Then
Return TimeSpan.Zero
Else
Return Me.Date.Subtract(value)
End If
End Function

Notice the special handling of empty SmartDate values. Adding or subtracting any value to an
empty value results in an empty or zero value as appropriate. In any other case, the addition or sub-
traction is delegated to the actual underlying
DateTime value in mDate.
Overloading Operators
To make SmartDate as similar to DateTime as possible, it needs to overload the operators that are
overloaded by
DateTime, including equality, comparison, addition, and subtraction.
Equality
Equality and inequality operators delegate to the override of the Equals() method:
Public Overloads Overrides Function Equals(ByVal obj As Object) As Boolean
If TypeOf obj Is SmartDate Then
Dim tmp As SmartDate = DirectCast(obj, SmartDate)
If Me.IsEmpty AndAlso tmp.IsEmpty Then
Return True
Else
Return Me.Date.Equals(tmp.Date)
End If
ElseIf TypeOf obj Is Date Then
Return Me.Date.Equals(DirectCast(obj, Date))
ElseIf TypeOf obj Is String Then
Return Me.CompareTo(CStr(obj)) = 0
Else
Return False
End If
End Function
CHAPTER 5 ■ COMPLETING THE FRAMEWORK 275
6315_c05_final.qxd 4/13/06 12:36 PM Page 275
Public Shared Operator =( _
ByVal obj1 As SmartDate, ByVal obj2 As SmartDate) As Boolean

Return obj1.Equals(obj2)
End Operator
Public Shared Operator <>( _
ByVal obj1 As SmartDate, ByVal obj2 As SmartDate) As Boolean
Return Not obj1.Equals(obj2)
End Operator
The Equals() method is relatively complex. This is because it supports the idea of comparing
a
SmartDate to another SmartDate, to a String value, or to a regular DateTime value. In each case, it
honors the idea of an empty date value.
Then the equality and inequality operators simply delegate to the
Equals() method. There are
overloads of the equality and inequality operators to allow a
SmartDate to be directly compared to
a
DateTime or String value.
Comparison
In addition to equality
, it is possible to compare
SmartDate values to see if they ar
e greater than or
less than another
SmartDate, String, or DateTime value. This is easily accomplished given the imple-
mentation of the
CompareTo() methods earlier. For instance, here are a couple of the comparison
operators:
Public Shared Operator >( _
ByVal obj1 As SmartDate, ByVal obj2 As SmartDate) As Boolean
Return obj1.CompareTo(obj2) > 0
End Operator

Public Shared Operator <( _
ByVal obj1 As SmartDate, ByVal obj2 As SmartDate) As Boolean
Return obj1.CompareTo(obj2) < 0
End Operator
Along with greater than and less than, there are greater than or equals, and less than or equals
oper
ators that work in a similar manner. And as with equality and inequality, there are overloads of
all these operators for
String and DateTime comparison as well.
A
ddition and S
ubtraction
The Add() and Subtract() methods implemented earlier are also made available through operators:
Public Shared Operator +( _
ByVal start As SmartDate, ByVal span As TimeSpan) As SmartDate
Return New SmartDate(start.Add(span), start.EmptyIsMin)
End Operator
Public Shared Operator -( _
ByVal start As SmartDate, ByVal span As TimeSpan) As SmartDate
Return New SmartDate(start.Subtract(span), start.EmptyIsMin)
End Operator
CHAPTER 5 ■ COMPLETING THE FRAMEWORK276
6315_c05_final.qxd 4/13/06 12:36 PM Page 276
Public Shared Operator -( _
ByVal start As SmartDate, ByVal finish As SmartDate) As TimeSpan
Return start.Subtract(finish.Date)
End Operator
Combined, all these methods and operators mean that a SmartDate can be treated almost
exactly like a
DateTime.

Database Format
The final bit of code in SmartDate exists to help simplify data access. This is done by implementing
a method that allows a
SmartDate value to be converted to a format suitable for writing to the data-
base. Though
SmartDate already has methods to convert a date to text and text to a date, it doesn’t
have any good way of getting a date formatted properly to write to a database. Specifically, it needs
a way to either write a valid date or write a null value if the date is empty.
In ADO.NET, a null value is usually expressed as
DBNull.Value, so it is possible to implement
a method that returns either a valid
DateTime object or DBNull.Value:
Public ReadOnly Property DBValue() As Object
Get
If Me.IsEmpty Then
Return DBNull.Value
Else
Return Me.Date
End If
End Get
End Property
Since SmartDate already implements an IsEmpty() property, the code here is pretty straightfor-
ward. If the value is empty,
DBNull.Value is returned, which can be used to put a null value into a
database via ADO.NET. Otherwise, a valid date value is returned.
At this point, you’ve seen the implementation of the core
SmartDate functionality. While using
SmartDate is certainly optional, it does offer business dev
elopers an easy way to handle dates that
must be represented as text, and to support the concept of an empty date. Later in the chapter, the

SafeDataReader will also include some data access functionality to make it easy to save and restore
a
SmartDate from a database.
This same approach can be used to make other data types “smart” if you so desire. Even with
the
Nullable(Of T) suppor
t from the .NET Framework, dealing with empty values often requires
extra coding, which is often most efficiently placed in a framework class like
SmartDate.
Common Business Rules
The BusinessBase class
implemented in Chapter 3 includes support for validation rules. Each rule
is a method with a signature that conforms to the
RuleHandler delegate. A business object can
implement business rules conforming to this delegate, and then associate those rule methods
with the properties of the business object.
M
ost applications use a relatively small, common set of validation rules—such as that a string
value is required or has a maximum length, or that a numeric value has a minimum or maximum
value. Using reflection, it is possible to create highly reusable rule methods—which is the purpose
behind the
Csla.Validation.CommonRules class.
Ob
viously
, using r
eflection incurs some performance cost, so these reusable rule methods may
or may not be appropriate for every application. However, the code reuse offered by these methods
CHAPTER 5 ■ COMPLETING THE FRAMEWORK 277
6315_c05_final.qxd 4/13/06 12:36 PM Page 277

×