Access class properties with MyClass.Item syntax

J

Joel Reinford

I would like to build a class that has properties which can be accessed by
string names or index numbers in the form of MyClass.Item("LastName"). The
string names or item index values would be populated by a data-driven loop.
I need a few pointers to get me started in the right direction. I'm pretty
sure that I need a default Item property but I'm not sure how to create that
or index the other properties. A short code example is below


Joel Reinford


Assume a DataTable with four columns: FirstName, LastName, Birthdate, Gender

Function CreatePerson(dt as DataTable) as Person
Dim p as New Person
Dim dc As DataColumn
Dim drw As DataRow = dt.Rows(0)

For Each dc In dt.Columns
'this is what I want to achieve, it would translate into
p.FirstName, p.LastName, etc.
p.Item(dc.ColumnName) = drw(dc.ColumnName)
Next

End Function


Here is what a sample class looks like now
Public Class Person

Private _FirstName As String
Private _LastName As String
Private _LastName As String
Private _Gender As String
Private _Birthdate As Date

Property FirstName() As String
Get
Return _FirstName
End Get
Set(ByVal Value As String)
_FirstName = Value
End Set
End Property


Property LastName() As String
Get
Return _LastName
End Get
Set(ByVal Value As String)
_LastName = Value
End Set
End Property


Property Gender() As String
Get
Return _Gender
End Get
Set(ByVal Value As String)
_Gender = Value
End Set
End Property


Property Birthdate() As Date
Get
Return _Birthdate
End Get
Set(ByVal Value As String)
_Birthdate = Value
End Set
End Property

End Class
 
J

Jay B. Harlow [MVP - Outlook]

Joel,
The "easiest" way is to add an indexed Item property with a Select Case for
each field

You can overload the Item Property for both Name & Index.

Something like:
Public Class Person

Private _FirstName As String
Private _LastName As String
Private _LastName As String
Private _Gender As String
Private _Birthdate As Date

Property FirstName() As String
Get
Return _FirstName
End Get
Set(ByVal Value As String)
_FirstName = Value
End Set
End Property


Property LastName() As String
Get
Return _LastName
End Get
Set(ByVal Value As String)
_LastName = Value
End Set
End Property


Property Gender() As String
Get
Return _Gender
End Get
Set(ByVal Value As String)
_Gender = Value
End Set
End Property


Property Birthdate() As Date
Get
Return _Birthdate
End Get
Set(ByVal Value As String)
_Birthdate = Value
End Set
End Property

Default Public Property Item(ByVal name As String) As Object
Get
Select Case name
Case "FirstName"
Return _FirstName
Case "LastName"
Return _LastName
Case "Gender"
Return _Gender
Case "Birthdate"
Return _Birthdate
Case Else
Throw New ArgumentOutOfRangeException("name", name,
"Invalid property name")
End Select
End Get
Set(ByVal value As Object)
Select Case name
Case "FirstName"
_FirstName = DirectCast(value, String)
Case "LastName"
_LastName = DirectCast(value, String)
Case "Gender"
_Gender = DirectCast(value, String)
Case "Birthdate"
_Birthdate = DirectCast(value, Date)
Case Else
Throw New ArgumentOutOfRangeException("name", name,
"Invalid property name")
End Select
End Set
End Property

Default Public Property Item(ByVal index As Integer) As Object
Get
' similar select case as above
End Get
Set(ByVal value As Object)
' similar select case as above
End Set
End Property
End Class

Including Default on the Item property allows you to drop Item

Dim name As string
name = MyClass.Item("LastName")
name = MyClass("LastName")

Using Reflection you can get ride of the Select Case, post if you want to
know how to do it with Reflection.

Hope this helps
Jay
 
J

Joel Reinford

Jay:

Yes, I'd like to avoid the Select Case. My reason for doing this is to save
some code in other places and it seem like I'd just be swapping where the
lines of code are. I'd like some input on how to use Reflection. Also, any
thoughts on the late binding in Reflection and performance hit.


Joel
 
J

Jay B. Harlow [MVP - Outlook]

Joel,
Here's a version that uses Reflections (the System.ComponentModel really,
which is based on Reflection).
Public Class Person

Private _FirstName As String
Private _LastName As String
Private _LastName As String
Private _Gender As String
Private _Birthdate As Date

Property FirstName() As String
Get
Return _FirstName
End Get
Set(ByVal Value As String)
_FirstName = Value
End Set
End Property


Property LastName() As String
Get
Return _LastName
End Get
Set(ByVal Value As String)
_LastName = Value
End Set
End Property


Property Gender() As String
Get
Return _Gender
End Get
Set(ByVal Value As String)
_Gender = Value
End Set
End Property


Property Birthdate() As Date
Get
Return _Birthdate
End Get
Set(ByVal Value As String)
_Birthdate = Value
End Set
End Property
Private Shared ReadOnly m_properties As
System.ComponentModel.PropertyDescriptorCollection

Shared Sub New()
m_properties =
System.ComponentModel.TypeDescriptor.GetProperties(GetType(Person))
End Sub

Default Public Property Item(ByVal name As String) As Object
Get
Return m_properties(name).GetValue(Me)
End Get
Set(ByVal value As Object)
m_properties(name).SetValue(Me, value)
End Set
End Property

Default Public Property Item(ByVal index As Integer) As Object
Get
Return m_properties(index).GetValue(Me)
End Get
Set(ByVal value As Object)
m_properties(index).SetValue(Me, value)
End Set
End Property
End Class

The Item(string) property should really throw an ArgumentOutOfRangeException
or similar when passed an invalid property name, I will leave that as an
exercise for you.

Hope this helps
Jay
 
J

Joel Reinford

Jay:

That's what I was looking for. This is also much more maintainable than the
Select Case method. I can add/subtract properties without needing to change
anything else. Thanks very much.

Joel
 
J

Jay B. Harlow [MVP - Outlook]

Joel,
Also, any
thoughts on the late binding in Reflection and performance hit.
I use reflection where it actually makes sense to use reflection.

I use late binding (Option Strict Off) where it makes sense to use late
binding. (such as some COM interop).

I rarely code for performance first, rather I code for "Correctness" first
(primarily OOP), then will code for performance when a routine is proven to
have a performance problem. I find most OOP designs lend themselves to being
rather performant from the start. (such as using polymorphism in favor of
conditionals (select case)).

Hope this helps
Jay
 
J

Jay B. Harlow [MVP - Outlook]

Joel,
In addition to my other post, thinking about this. It appears that you
really want to use a Typed DataSet! Especially if you already have your data
in a DataTable! And you domain objects don't really have any logic (they are
only properties).

As a Typed DataSet gives you type safe properties, plus allows you to index
those properties by Name or Index.

Then you can "avoid" "coding" the Person class itself & all those
properties...


Remember that Typed DataSets are still OO!

Martin Fowler's book "Patterns of Enterprise Application Architecture" from
Addison Wesley http://www.martinfowler.com/books.html#eaa explains when you
may want to use a traditional Domain Model & Data Mapper pattern:
http://www.martinfowler.com/eaaCatalog/domainModel.html
http://www.martinfowler.com/eaaCatalog/dataMapper.html

verses a Table Module & Data Gateway patterns:
http://www.martinfowler.com/eaaCatalog/tableModule.html
http://www.martinfowler.com/eaaCatalog/tableDataGateway.html

Martin also offers a couple of other useful patterns that can be used
instead of or in conjunction with the above patterns.

The System.Data.DataTable is an implementation of a Record Set pattern:
http://www.martinfowler.com/eaaCatalog/recordSet.html

Rockford Lhotka's book "Expert One-on-One Visual Basic .NET Business
Objects" from A! Press provides a pre-implemented variation of Fowler's
Domain Model & Data Mapper patterns.
http://www.lhotka.net/



Generally if there is no real logic behind my domain objects, I would use
the DataSet OOM coupled with a Table Module & Data Gateway patterns. As the
classes themselves are not really living up to their potential! :) The
Table Module & Data Gateway patterns may be implemented in a single class or
two classes. Again I would consider using a Typed DataSet.

However if there is significant logic behind my domain objects, I would then
favor the Domain Model & Data Mapper patterns.

Depending on the needs of the project I would consider Fowler's other
patterns...

Hope this helps
Jay
 
J

Joel Reinford

Jay:

Thanks for the information. I used the datatable example only because I
thought it would make my question easy to understand. My real usage for this
at the moment is matching SQL output parameters to a business entity class
object. I'm using the DAAB SqlHelper method to get the parameters from the
stored procedure and I want to match the values in the output parameters to
the properties in the business entity class. Right now, that involves
declaring the parameter,
using the parameter, and retrieving the parameter value. If I can match the
parameter name to the property name, I should be able to do that with a
loop, rather than the tedious job of writing several lines of code for each
parameter.

Having said all that, I will definitely check out the links. There is always
more to learn and better ways to do things. Again, I appreciate your help

Joel
 
J

Jay B. Harlow [MVP - Outlook]

Joel,
Another alternative would be to take my code and create your own
Adapter/DataMapper that populated the domain object from a
DataReader/DataCommand. Without adding an Item property to each domain
object. For me: the Item property feels like a "back door" to the object
that should not be part of the domain object, the logic that I gave for the
Item Property feels more correct in the Data Mapper...


I prefer the Data Mapper model where the Data Mapper returns a wholly
constructed domain object, by calling the constructor of the Domain object
with each value it needs, this unfortunately can lead to duplication in
validation code, which needs to be mitigated...

Hope this helps
Jay
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top