I don’t have any code today, because I was working the polls yesterday, and spent most of today’s worktime tuning that file copier. I hit a big problem because of some confusing, mis-coded loops. Here’s the scenario in pseudocode (I don’t want to fire up the Windows box just to get this code).
for each item in collectionA if type of item is Layer then processLayer item else if type of item is LayerGroup then for each subItem in item if type of subItem is Layer then processLayer subItem else if type of subItem is Raster processRaster subItem end if end for end if end for
If you can really read this, it does a 1-level deep exploration when the item is a LayerGroup.
It’s implid that an item can be a Layer, a LayerGroup, or a Raster.
Well, the problem was that sometimes, Rasters were at the top level. Oooops. The fix is to use a recursive function. A recursive function is a function that calls itself.
That definition usually doesn’t explain, too well, to most people, what a recursive function is. A recursive function, in this situation, is used to process a hierarchy of objects. In the same way that a loop is useful to process a list of objects, a recursive function is useful to process a hierarchy of objects. Whenever we have an object that contains other objects, the recursive function is used on that object.
Here’s the new code:
define function F( collectionA ) for each item in collectionA if type of item is Layer then processLayer item else if type of item is Raster then processRaster item else if type of item is LayerGroup then F( item ) end if end for end for
This way of doing it is not just more compact, it’s also lacking the bug. It will also process not only two-layer hierarchies: it’ll handle all hierarchies.
Now, if you already write recursive functions, you may not believe this, but the original algorithm above was being used. It’s just more evidence that it’s still pretty hard to grasp recursion. It doesn’t really make sense until you use it to do something like seek files in a directory hierarchy. Even then, it might not make sense to everyone.
These days, recursion is not so common anymore, because a lot of apps use database tables rather than hierarchies of files.
Denouement
There’s a slightly unhappy “undoing” to this lesson. The problem is that the type of the initial collection differs from the type of the collection within the hierarchy. I operated under the assumption that I could use the Object object to hold the collection. In fact, I had to coerce the object to one of two types to do what I wished.
My error requires (as far as I know) that I, basically, duplicate code, with one for each type of collection. Perhaps there’s a solution available using generic programming, and that I just don’t know it yet.
Here’s the code:
Public Function ProcessLayerSourceCollection(ByVal lsc As Object) As Collection Dim cOutput As Collection = New Collection() Dim pFeatureLayer As IFeatureLayer Dim pRasterLayer As IRasterLayer Dim pCompositeLayer As ICompositeLayer Dim s As String Dim i As Integer If TypeOf lsc Is ICompositeLayer Then Dim c As ICompositeLayer = lsc For i = 0 To c.Count - 1 If TypeOf c.Layer(i) Is IFeatureLayer Then pFeatureLayer = c.Layer(i) For Each s In LayerSources(pFeatureLayer) cOutput.Add(s) Next ElseIf TypeOf c.Layer(i) Is IRasterLayer Then pRasterLayer = c.Layer(i) For Each s In RasterSources(pRasterLayer) cOutput.Add(s) Next For Each s In InfoDirContents(pRasterLayer.FilePath()) cOutput.Add(s) Next ElseIf TypeOf c.Layer(i) Is ICompositeLayer Then pCompositeLayer = c.Layer(i) For Each s In ProcessLayerSourceCollection(pCompositeLayer) cOutput.Add(s) Next End If Next ElseIf TypeOf lsc Is IMap Then Dim c As IMap = lsc For i = 0 To c.LayerCount - 1 If TypeOf c.Layer(i) Is IFeatureLayer Then pFeatureLayer = c.Layer(i) For Each s In LayerSources(pFeatureLayer) cOutput.Add(s) Next ElseIf TypeOf c.Layer(i) Is IRasterLayer Then pRasterLayer = c.Layer(i) For Each s In RasterSources(pRasterLayer) cOutput.Add(s) Next For Each s In InfoDirContents(pRasterLayer.FilePath()) cOutput.Add(s) Next ElseIf TypeOf c.Layer(i) Is ICompositeLayer Then pCompositeLayer = c.Layer(i) For Each s In ProcessLayerSourceCollection(pCompositeLayer) cOutput.Add(s) Next End If Next Else Debug.WriteLine("Unknown type of lsc.") End If Return cOutput End Function
It’s still slightly better than the original, because it displays the explicit parallels between the two parts. That will make it a little easier to maintain. A slightly better factoring would have been to make separate functions to process each type.