1

I am a relative VB.Net noob, and I'm learning by doing. I'm sure what I'm about to ask has been asked 10^19 times before, but whatever code word it's under, I can't figure out how to Google it. Here goes...

We have an object model with one or more Project objects that consists of several Tables, which contain Rows which have Fields. This leads to code all over our apps that looks something like this...

Dim theColor As String = Projects(1).Tables(5).Rows(22).Fields(3)

In our application, if any of the objects in this "call chain" does not exist, the correct value for theColor should be Nothing*. This is just like Excel - the value of an empty cell in an empty row is vbnull, not "Excel has crashed".

This is not how VB.Net works, however. If, for instance, Rows(22) does not exist, the Fields(3) is called on Nothing and an exception is thrown. My question is how to best deal with this...

1) I could check each value to see it it's not Nothing, but that leads to horrible amounts of code...

If Projects(1) IsNot Nothing AndAlso Projects(1).Tables(5) AndAlso...

We have thousands of these, the amount of code this would require would be enormous.

2) I could wrap all accessors in try/catch, but that's really just a different sort of (1)

3) I could have a special instance of each object that has empty values. So, for instance, Tables(5) returns NullTable and Row(22) returns NullRow. But this means I have to always use accessor methods, I can't just look in the underlying arrays. You're probably saying good, but sadly a lot of our older code does just that (yes, duh).

4) Something else entirely? Am I missing some magic that everyone other than me knows?

Maury Markowitz
  • 9,082
  • 11
  • 46
  • 98
  • It might not fit the context, but you can do something like" `Dim myRow As RowObjectThing = Projects(1).Tables(5).Rows(22)` then for the rest of that procedure just use `myRow` to drill into it, or `myTable` in other cases. This is often handy just to prevent all that typing. – Ňɏssa Pøngjǣrdenlarp Jan 14 '15 at 17:25
  • Sure, but then what if Tables(5) is null - or as is actually the case 90% of the time, Row(22). I can't know a-priory whether or now "22" exists, that's coming from other data. – Maury Markowitz Jan 14 '15 at 17:40
  • but the test for Nothing *can* be encapsulated all the way down the chain. – Ňɏssa Pøngjǣrdenlarp Jan 14 '15 at 17:51
  • If you have the option to upgrade to VB 14 / Visual Studio 2015 when it comes out, the [null-propagating operator](http://msdn.microsoft.com/en-us/magazine/dn890368.aspx) will come in handy here... Your code turns into `Dim theColor As String = Projects(1)?.Tables(5)?.Rows(22)?.Fields(3)`. If any of those intermediate accessors returns null, the whole right-hand side assigns null to your variable. – avanek Jan 14 '15 at 17:55
  • @avanek Excellent, this is precisely what I want! And suddenly I see where Swift got its syntax... :-) – Maury Markowitz Jan 14 '15 at 20:18

3 Answers3

1

You could have a function called GetField

Instead of

Dim theColor As String = Projects(1).Tables(5).Rows(22).Fields(3)

You would have

Dim theColor As String = GetField(1, 5, 22, 3)

Function GetField(ByVal projectIndex As Integer, ByVal tableIndex As Integer, ByVal rowIndex As Integer, byVal fieldIndex As Integer) As Object

    If Projects(projectIndex) Is Nothing Then
        Return Nothing
    End If

    If Projects(projectIndex).Tables(tableIndex) Is Nothing Then
        Return Nothing
    End If

    If Projects(projectIndex).Tables(tableIndex).Rows(rowIndex) Is Nothing Then
        Return Nothing
    End If

    If Projects(projectIndex).Tables(tableIndex).Rows(rowIndex).fields(fieldIndex) Is Nothing Then
        Return Nothing
    End If

    Return Projects(projectIndex).Tables(tableIndex).Rows(rowIndex).fields(fieldIndex)
End Function

But I got to say... what you are doing looks sloppy. You should think of using classes with properties.

the_lotus
  • 12,668
  • 3
  • 36
  • 53
  • Maybe that last sentence is my #4... can you explain why properties would help? – Maury Markowitz Jan 14 '15 at 17:01
  • Plus1... I agree that a class/properties approach would be better but your solution is quite neat and probably quickly implemented in Maury's code. – Mych Jan 14 '15 at 17:11
  • @MauryMarkowitz having numbers like this all over the place is not a good idea. Maintaining the application will be a nightmare and you have to make sure the numbering will never change. .Fields(3) doesn't mean anything, but .Color is a lot more clear. Also, I would assume that Fields(3) returns an object, while a property would return the proper type. – the_lotus Jan 14 '15 at 17:25
  • Oh sorry, those numbers are actually consts, but they're on my other machine and I didn't want to type out "dbTableUnitsFieldName" a bunch of times :-) – Maury Markowitz Jan 14 '15 at 17:44
  • Ok, lotus and mych, can you explain exactly what you mean? What class needs to be defined and with what properties? – Maury Markowitz Jan 14 '15 at 17:53
  • I don't think this code will work if the index is out of range – SSS Jan 14 '15 at 23:28
0

You could concoct a "project manager" of sorts. It is hard to know how viable this is from the post, but something like:

Class ProjectManager
    Public Property CurrentProject As ProjectThing
    Public Property CurrentTable As Integer
       Get
          Return tblIndex
       End Get
       Set
          If CurrentProject IsNot Nothing Then
              If CurrentProject.Tables(value) Is Nothing Then
                  Throw exception
              Else
                  tblIndex = value
              End If
          End If
       End Set
    End Property

    ' etc

Then use it to store the current reference to the project and/or table. All the Is Not Nothings can be embedded there.

Private myPrj As New ProjectManager
...
myPrj.CurrentProject = Project(1)
myPrj.CurrentTable = 5

With the "manager" doing all the checking for everyone, you dont have to (much):

Dim theColor As String = myPrj.Rows(22).Fields(3)

or

Dim theColor As String = myPrj.GetRowValue(22, 3)

What it would really be doing is storing a validated object references for you, and testing those not worth storing. It could go as deep as you needed. Even if all it really did was encapsulate those Is Nothing/Is Not Nothing tests, it might add some value.

Public Function GetRowValue(r As Integer, f as Integer) As Something 
    If r < CurrentProject.Tables(tblIndex).Rows.Count AndAlso 
         f < CurrentProject.Tables(tblIndex).Rows(r).Fields.Count  Then
              Return ...

 'or 
 Public Function GetRowValue(Of T)(r As Integer, f as Integer) As T

Once a project is specified, it could expose helpful properties like TableCount. It is possible that the data represented by some of the most used, most important Const definitions, could be exposed as properties:

 ' swap a property interface for "3"
 Dim theColor As String = myPrj.FooColor(22)        
Ňɏssa Pøngjǣrdenlarp
  • 38,411
  • 12
  • 59
  • 178
  • I implemented something like this... a GetValue that takes a dot-notation "path" to the field... GetValue("Project(1).Table(5).Row(22)). It returns Nothing if anything is Nothing. It works, but producing the "path" is onerous, and it kind of hides the semantics. But maybe that's the whole problem... I'm opening another thread on that. – Maury Markowitz Jan 14 '15 at 17:52
  • I had an insurance app where each plan has many policies, each policy has many tables with many values; many riders and many options. A "broker" to hold onto the validated upper objects helps quite a bit. The GetValue above obviates the top 2 parts of the path which is the point. – Ňɏssa Pøngjǣrdenlarp Jan 14 '15 at 17:57
0

You can handle the exception:

Dim theColor As String = Nothing
Try
  theColor = Projects(1).Tables(5).Rows(22).Fields(3)
Catch
End Try

If you want to do it 'properly' you should specify the exceptions you are guarding against:

Dim theColor As String = Nothing
Try
  theColor = Projects(1).Tables(5).Rows(22).Fields(3)
Catch ex As IndexOutOfRangeException
Catch ex As NullReferenceException
End Try
SSS
  • 4,807
  • 1
  • 23
  • 44