LINQ and the AutoCAD .NET API (Part 1)
Motivation
This is the first in a series of posts on LINQ an the AutoCAD .NET API. Here's a complete list of posts in this series.
Introduction
I recently played around with the AutoCAD .NET API and I want to share some ideas I had on how to make use of IEnumerable<T>
when dealing with the drawing database. Generally speaking, the AutoCAD API is very powerful as the drawing is based on a database and you use transactions to interact with the drawing data. If something goes wrong, you simply abort the transaction and your changes are rolled back. This is nice, but comes with the cost of writing a lot of boilerplate code.
Boilerplate code
As an example, to display the names of all layers you have to do the following (the code is taken from the AutoCAD .NET developer’s guide):
Well, there's a lot going on here. This actually means, that there's a lot of boilerplate code involved which we actually don't want to deal with. Furthermore one has to have a lot of explicit knowledge about the structure of the API, which may not be obvious to a beginner:
- We have to start a transaction and it has to be disposed of in the end (line 9)
- Layers are stored in a table of type
LayerTable
(line 12) - The database object has a property
LayerTableID
which is the ID for the table we're interested in (line 13) - We get the
LayerTable
object from the transaction viaGetObject
and we have to cast it appropriately (line 13) - We have to iterate the layer table to get the IDs of the single layers (line 17)
- Layer objects are of type
LayerTableRecord
(line 19) - We get the
LayerTableRecord
objects from the transaction viaGetObject
and we have to cast them appropriately (line 20)
An Add-In developer's perspective
From an AutoCAD Add-In developer's perspective, do we really want to care about all this stuff? In listing 1, we actually want to somewhow get the layer objects and display their names. So, as we are dealing with a collection of layers, it would be interesting to find a way to use an implementation of IEnumerable<T>
to get rid of the transaction and database specific code and "hide" it from the client code.
How can we do that? Let's start simple: we define a static class called LayerHelper
that has one single method called GetLayers
, which returns an IEnumerable<LayerTableRecord>
:
OK, a very simple interface. The signature of GetLayers()
already tells us what we get, an enumerable of LayerTableRecord
s. So we don't have to deal with IDs, we simply get the layer objects. Now we have to find a way to return all layers in the drawing database. We already have this code in listing 1. So, to start simple, let's copying and pasting the example code into our GetLayers()
method:
Looks good. The main modification we made is that we removed the part where we collect the layer names and instead yield the LayerTableRecord
objects. The transaction handling and the ID stuff is hidden in the GetLayers
method. So, if we want to display the layer names like in listing 1, we can use our helper method like this (we modify the initial code and use a StringBuilder
and string interpolation instead of string concatenation for the remaining code samples):
Looks pretty cool! Our client code now just deals with our buisness logic (collecting the layer names and displaying them). We also have a single entry point, the LayerHelper
class, and the GetLayers
method gives us what we actually want, all layer objects. And the heavy lifting is hidden in the GetLayers
method.
The problem
A very nice implementation, but most cool things have a catch, so there is one somewhere, right? Well, yes, there is a catch. The problem is that we must not use AutoCAD objects after the transaction they've been created with was disposed of. It's not a problem in listing 4, but in general our implementation of GetLayers()
is flawed. Let's look at another example:
This is almost the same as the code in listing 4, but the problem is in line 5. ToList()
yields all layer objects and immediately after that the transaction is disposed of. So in line 9 we're using objects that are unsafe to access. Getting the Name property in listing 4 works, but we should not do it. We'll go into the details of this whole issue in the next post. For now, let us just fix the problem (so we don't leave a post with code that may not work).
And a fix
We add a Database
and a Transaction
parameter to our GetLayers
method:
Unfortunately our client code is now less clean. We still don't have to deal with the IDs, nor do we have to write code to pull the objects out of the database. But the transaction is back in our client code. On the other hand, the code is still nicer than listing 1 and we now can safely use ToList()
:
And we now can use LINQ queries on our layers. Let's display all layer names that start with a given prefix and sort them alphabetically:
What's next
In the next post we'll go into the details of the here described error and we'll have a look on how to correctly handle transactions and their objects.