.NET has a bountiful cornucopia of classes available for you to use in your AVR applications. For example, there are many data structures in System.Collections, System.Collections.Specialized, and System.Collections.Generic. RPG programmers tend to think rather rigidly about data structures—thinking only data structures being represented by RPG’s intrinsic record format-like capabilities. But the data structures .NET offers are vastly richer and more powerful than anything green screen RPG offers. In .NET, stacks, queues, lists, dictionaries, hash tables, and collections offer a nearly endless way of working with your data.
Prior to .NET 2.0, .NET only offered loosely-typed structures. That is, the contents of the data structures weren't limited to a single object type, you could put any object in and then that object had to be cast when fetching it back out. Starting in AVR 8x, AVR can use the strongly-typed collections found in System.Collections.Generic. This constrains the data structure to a specific object type. There is a slight case of unforunate naming with these "generic" data structures. It could be argued that "generic" is a better word to identify loosely-typed structures; and that "specific" or some such word should have been used for strongly-typed objects. In this case, "generic" means that at compile-time the otherwise generic collection will be made specific. Don't get hung up on this naming wart--the convenience of strongly-typed generic collections makes up for their silly naming.
Let's take a look at using a stack. A stack is a last-in, first-out structure. The canonical example of a stack is a stack of plates in a cafeteria. Plates are pushed onto the stack—and the first one removed ("popped" in computer science parlance) is the last one to have been added ("pushed") onto the stack. Contrast this behavior to a queue, which is a first-in, first-out structure.
We'll consider a stack in the context of working with program exceptions. Exceptions are raised as a response to an error (usually an unexpected error). It's a good practice to explicitly monitor for unhandled exceptions and if one occurs, show the error to the user gracefully (as gracefully as you can considering that the user can't recover from an unhandled exception!).
What we'd like to do is show the user the Message property (and perhaps the StackTrace) from the exception. However there's a rub—exceptions all derive from System.Exception and one of System.Exeptions's properties is InnerException, which itself is another exception. .NET Exceptions are like nested Russian dolls. And generally, the most meaningful exception is in the innermost exception (the one that occurred first). We'd like to reveal all of the exceptions to the user, but we want to reveal from the inside out. We'll use a stack to do that.
First, for the sake of our example, we'll dummy up a nested exception object with this code:
DclFld Error1 Type( Exception ) New( "Error level 1" ) DclFld Error2 Type( Exception ) New( "Error level 2", Error1 ) DclFld Error3 Type( Exception ) New( "Error level 3", Error2 )
Figure 1. Declaring a three-deep nested exception object.
The InnerException property doesn't have a setter method, it can only be created through a constructor. So in the code above, the inner-most exception is Error1, which is nested in Error2, and Error2 is nested in Error3. We want to report these errors from the inside out.
Note that to really handle exception errors, you won't need to declare a nested exception object—you get one for free when your code fails!
We could use the code below in Figure 2a to report the errors, but it starts with the outer-most error and reports inward. This is last-in, first-out order and that's not what we want. We want first-in, first-out.
DclFld CurrentError Type( Exception ) DclFld ErrorStack Type( Generic.Stack( *Of Exception ) ) New() // Show the errors from the outside in. CurrentError = Error DoUntil ( CurrentError = *Nothing ) Console.WriteLine( CurrentError.Message ) CurrentError = CurrentError.InnerException EndDo
Figure 2a. Showing the errors from the outside in.
Line 5 in Figure 2a is of special interest. It declares the variable ErrorStack to be of type System.Collections.Generic.Stack and that its entries are Exception objects exclusively. This type of object is known as a generic object—that is, it can be a list of any object, but you need to specify the type of object is should contain at compile-time. We'll see a payoff for this strongly-typed stack in a bit.
To report the errors first-in, first-out, we first traverse over the errors, starting with outer error, and push each error onto a stack (lines 7 through 12 in Figure 2a). This results in a stack with the top "plate" being Error1, the second being Error2, and the third being Error3. That is, Error3 was pushed onto the stack, then Error2, and then Error1. The last error pushed is at the top of the stack.
Now it's easy to report the errors in the correct order. Lines 14 through 18 in Figure 2b traverse the stack from the top down, reporting the errors in the order we want to present them. Line 16 does two things: it assigns the top-most entry on the stack to the CurrentError variable, and it removes ("pops" in stack parlance) that top-most entry off of the stack. It does this until the stack is empty.
Because you can't get to the inner-most error directly—you have to traverse from the outside in, the double processing required to get the errors in the correct order can't be avoided. However, it's a relatively small amount of processing. Also, realize that the stack doesn't create a duplicate list of error objects; rather it simply keeps track of references back to the original error objects. Memory usage is minimal with this technique to get the list in the correct order. However, when working with any of .NET's data structures, do remember you're populating real memory so don't get carried away trying to put hundreds of thousands of objects in memory.
DclFld CurrentError Type( Exception ) DclFld ErrorStack Type( Generic.Stack( *Of Exception ) ) New() // Stack up the errors from the outside in. CurrentError = Error DoUntil ( CurrentError = *Nothing ) ErrorStack.Push( CurrentError ) CurrentError = CurrentError.InnerException EndDo // Show the errors from the inside out. DoWhile ( ErrorStack.Count > 0 ) CurrentError = ErrorStack.Pop() Console.WriteLine( CurrentError.Message ) EndDo
Figure 2b. Showing the errors from the inside out.
There is one more thing to notice in Figure 2b. It's already been noted that line 5 is declaring a strongly-typed stack that can contain only Exception objects. One of the payoffs of using the strongly-typed stack is that line 16 doesn't require a cast to do its assignment. Had we been using a loosely-typed stack (the Stack object in the (System.Collections.Specialized namespace), we would have had to cast the object like this:
CurrentError = ErrorStack.Pop() *As Exception
The lack of casting is a small part of the payoff, really. The larger payoff is you know, without question, that our strongly-typed stack object contains nothing but Exception objects. With the loosely typed stack object, any object could have been pushed onto the stack.
There may be value in creating a "ragged," loosely-typed collection (where element types vary), and if that's what you need, use those data structures that reside in System.Collections.Specialized. However, for many uses, it's better to use the strongly-typed versions out of the System.Collections.Generic namespace.
As you can see, it's easy to work with .NET's data structures and they can be used to solve lots of every day programming problems.