Tuesday, July 22, 2008

Using LINQ with ArcObjects

Are you using .NET 3.5 to do ArcObjects programming? Are you still manually enumerating through row or feature cursors? Listen up!

It's really very simple to get LINQ running with ArcObjects. All you have to do is add an extension method to either ICursor or IFeatureCursor like so:
public static class ICursorExtensions {

public static IEnumerable<IRow> AsEnumerable(this ICursor source) {
Func<IRow> next = () => source.NextRow();
IRow current = next();
while (current != null) {
yield return current;
current = next();

With your new extension method in hand, you can unleash LINQ on your ArcObjects code. You can now write code like this:
Int32 count = cursor.AsEnumerable().Count();

Or, you could write a query expression against two cursor objects:
var resultSeq =
from x in cursor1.AsEnumerable()
from y in cursor2.AsEnumerable()
where x.get_Value(0) = y.get_Value(0)
select new { Left = x, Right = y };

You can do a similar thing in F#:
type ICursor with
member c.AsEnumerable() =
seq {
let next = c.NextRow
let current = ref (next())
while !current <> null do
yield !current
do current := next()
let count = cursor.AsEnumerable() |> Seq.length

Sure beats manually manipulating an enumerator, doesn't it?


darkchip said...

Love to see a vb.net version. Have trouble with the code conversion.

Ray Vernagus said...

Unfortunately, there is no direct equivalent to this code in Visual Basic since the language does not support a "yield" operation. But, you can gather all of the rows in a List and pass the list out of the method:
Imports System.Collections.Generic
Imports System.Runtime.CompilerServices

Public Module ICursorExtensions
<Extension()> _
Public Function AsEnumerable(ByVal source As ICursor) As IEnumerable(Of IRow)
Dim nextRow As Func(Of IRow) = Function() source.NextRow()
Dim rows As New List(Of IRow)

Dim current As IRow = nextRow()
Do While current IsNot Nothing
current = nextRow()

Return rows
End Function
End Module
Hope that helps!

JBear said...

Great discussion! By the way, the latest version of VB does support iterator (Yield Return).

Even with the older version, you can do the same thing if declaratively like this:

Public Function AsEnumerable(ByVal source As ICursor) As IEnumerable(Of IRow)

Return Enumerable.Range(0, Integer.MaxValue) _
.Select(Function(n) source.NextRow()) _
.TakeWhile(Function(r) r IsNot Nothing)

End Function

Pleas also try my OR Mapping tool!


Code + Tool

It's written in C# though.