Let us look at something very common but not so clear actually about AutoCAD .NET coding in this post. Every AutoCAD .NET programmer has to do some object or entity type checking now and then, and there exist many different ways.
Let us name a few here, supposing C# is the coding language.
• Using the keyword ‘is’ to check what type the object of concern is.
• Retrieving the Type from the object of concern through calling the GetType() method against it and doing a comparison to the target type which can be got using the .NET typeof keyword.
• Comparing the ObjectClass instance of the object of concern with the target one.
• Comparing the DxfName property of the object of concern with an arbitrary string.
The first approach is the most concise one and many people like it:
if (obj is Circle)
The second is not so obvious but is also commonly used:
if (obj.GetType() == typeof(Circle))
Let us address the two most popular approaches first. A small quiz: which one is better?
Smart readers would say it depends.
Exactly! Depends on what then? May I ask again?
From my point of view, three major concerns about the object type checking, one is how easy it is to use, another is about performance, and the third is whether they can really achieve our goal.
Obviously, the first approach is easier to use, a single keyword with only two characters seems to do it all. The second has a few other things involved, getting the Type instance of the object, getting the Type instance out of the target class, and finally doing the comparison with the equal operator (==). Of course, the == operator can be replaced with the Equals method but it does not seem to bring any real difference here.
So, in terms of easy-to-use, the first approach wins for sure. Then what about performance? Is the first also better than the second?
Not exactly! Out of many people’s expectations, I believe, the performance of the second is better than the first. We will do a small tiny experiment to prove it.
Now let’s go to the third concern. Do the two approaches bring us exactly the same results?
Not really! The second will return true for only AutoCAD Circle entities, but the first will return true for any entities derived from AutoCAD Circle including itself. It is not a big deal here since Circle is a final node in the AutoCAD .NET class tree. Of course, if somebody has derived something from the AutoCAD Circle it will be different. Though it’s not recommended, it is possible!
That may explain why the first approach is slower than the second one for it has more stuffs to think about, very likely many political matters.
Now let us talk about two more approaches to check object type in AutoCAD .NET.
The ObjectClass approach, like:
if (id.ObjectClass == RXObject.GetClass(typeof(Circle)))
and the real AutoCAD way, using DXF name code:
if (id.ObjectClass.DxfName == "CIRCLE")
The third does not bring us any good as it has the most syntax but the worst performance.
However, the fourth DxfName comparison way is the fastest and way faster than the silver medal winner. It only uses about one-tenth time as the first most popular approach does.
Here are the test code and command:
[CommandMethod("TestVariousTypeCheck")]
public static void TestVariousTypeCheck_Method()
{
Editor ed = MgdAcApplication.DocumentManager.MdiActiveDocument.Editor;
DateTime begin = DateTime.Now;
int count = TypeCheckApproach1().Count;
TimeSpan elapsed = DateTime.Now.Subtract(begin);
ed.WriteMessage("Time elapsed in TypeCheckApproach1 for {0} circles: {1}\n", count, elapsed.TotalMilliseconds);
begin = DateTime.Now;
count = TypeCheckApproach2().Count;
elapsed = DateTime.Now.Subtract(begin);
ed.WriteMessage("Time elapsed in TypeCheckApproach2 for {0} circles: {1}\n", count, elapsed.TotalMilliseconds);
begin = DateTime.Now;
count = TypeCheckApproach3().Count;
elapsed = DateTime.Now.Subtract(begin);
ed.WriteMessage("Time elapsed in TypeCheckApproach3 for {0} circles: {1}\n", count, elapsed.TotalMilliseconds);
begin = DateTime.Now;
count = TypeCheckApproach4().Count;
elapsed = DateTime.Now.Subtract(begin);
ed.WriteMessage("Time elapsed in TypeCheckApproach4 for {0} circles: {1}\n", count, elapsed.TotalMilliseconds);
}
public static ObjectIdCollection TypeCheckApproach1()
{
ObjectIdCollection ids = new ObjectIdCollection();
Database db = HostApplicationServices.WorkingDatabase;
using (Transaction tr = db.TransactionManager.StartTransaction())
{
BlockTable bt = tr.GetObject(db.BlockTableId, OpenMode.ForRead) as BlockTable;
BlockTableRecord btr = tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForRead) as BlockTableRecord;
foreach (ObjectId id in btr)
{
DBObject obj = tr.GetObject(id, OpenMode.ForRead);
if (obj is Circle)
{
ids.Add(obj.Id);
}
}
tr.Commit();
}
return ids;
}
public static ObjectIdCollection TypeCheckApproach2()
{
ObjectIdCollection ids = new ObjectIdCollection();
Database db = HostApplicationServices.WorkingDatabase;
using (Transaction tr = db.TransactionManager.StartTransaction())
{
BlockTable bt = tr.GetObject(db.BlockTableId, OpenMode.ForRead) as BlockTable;
BlockTableRecord btr = tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForRead) as BlockTableRecord;
foreach (ObjectId id in btr)
{
DBObject obj = tr.GetObject(id, OpenMode.ForRead);
if (obj.GetType() == typeof(Circle))
{
ids.Add(obj.Id);
}
}
tr.Commit();
}
return ids;
}
public static ObjectIdCollection TypeCheckApproach3()
{
ObjectIdCollection ids = new ObjectIdCollection();
Database db = HostApplicationServices.WorkingDatabase;
using (Transaction tr = db.TransactionManager.StartTransaction())
{
BlockTable bt = tr.GetObject(db.BlockTableId, OpenMode.ForRead) as BlockTable;
BlockTableRecord btr = tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForRead) as BlockTableRecord;
foreach (ObjectId id in btr)
{
if (id.ObjectClass == RXObject.GetClass(typeof(Circle)))
{
ids.Add(id);
}
}
tr.Commit();
}
return ids;
}
public static ObjectIdCollection TypeCheckApproach4()
{
ObjectIdCollection ids = new ObjectIdCollection();
Database db = HostApplicationServices.WorkingDatabase;
using (Transaction tr = db.TransactionManager.StartTransaction())
{
BlockTable bt = tr.GetObject(db.BlockTableId, OpenMode.ForRead) as BlockTable;
BlockTableRecord btr = tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForRead) as BlockTableRecord;
foreach (ObjectId id in btr)
{
if (id.ObjectClass.DxfName == "CIRCLE")
{
ids.Add(id);
}
}
tr.Commit();
}
return ids;
}
Here is the output concrete data:
Command: TestVariousTypeCheck
Time elapsed in TypeCheckApproach1 for 5000 circles: 156.0002
Time elapsed in TypeCheckApproach2 for 5000 circles: 124.8003
Time elapsed in TypeCheckApproach3 for 5000 circles: 655.2011
Time elapsed in TypeCheckApproach4 for 5000 circles: 15.6
So it really depends on what you need, but please do not say the ‘is’ way is always the best just because you feel it or personally like it. Please do some small tiny extra work and let the data speaks for itself! Of course, there are some other ways, better or worse, or can meet different needs. We may address some more in the future.
Enjoy it! More coding gadgets will be created and demonstrated in the future. Please stay tuned.
The leading edge AutoCAD .NET Addin Wizard (AcadNetAddinWizard) provides various project wizards, item wizards, coders and widgets to help program AutoCAD .NET addins
Surprised by the result. Should not you try moving the RXObject.GetClass(typeof(Circle)) and the typeof(Circle) call outside of the loops ? And what about soft cast:
var circle = obj as Circle;
if (null != circle) {
// ...
}
Should be slower but if you need to cast (common case), could be faster.
Posted by: mdelanno | 04/20/2012 at 04:34 AM
It's not necessary. I just wanted all circle ids for later use. What's the point to cast it? I didn't even care to open it!
Posted by: Spiderinnet1 | 04/20/2012 at 05:29 AM
In your case, you don't have to cast it, but it's frequent to have to cast in order to operate on the object.
Posted by: mdelanno | 04/20/2012 at 06:32 AM
And for timing, you should use StopWatch
Posted by: mdelanno | 04/20/2012 at 06:33 AM
In terms of soft cast, you are right. People have to do it very often in their daily programming life though it is not necessary in the case as demonstrated here. And it is too obvious a fact to mention.
Also thanks for your StopWatch tip. It looks a bit more straightforward but not something new either. The TimeSpan works perfectly in this case too, and TimeSpan or StopWatch is not something the demonstration cares about either.
Posted by: Spiderinnet1 | 04/20/2012 at 03:52 PM
If you really meant to use the 'as' keyword to do the object type checking, please read on. The latest post should clarify your confusion.
Posted by: Spiderinnet1 | 04/20/2012 at 09:59 PM
As mentioned if you change option 3 code to what I've shown below it is the same speed as option 4 using 5000 circles but faster when you start increasing the number of circles:
RXClass CircleClass = RXClass.GetClass(typeof(Circle));
foreach (ObjectId id in btr)
{
if (id.ObjectClass == CircleClass)
{
ids.Add(id);
}
}
Posted by: Doozer | 04/21/2012 at 10:35 AM
Just to say I have used your software and it is very good! Thanks
Posted by: Doozer | 04/21/2012 at 10:41 AM
Thanks for liking the AutoCAD .NET Addin Wizard (AcadNetAddinWizard).
You are right. The RXClass.GetClass() call had quite some overheads.
The improved version yielded some quite different results for 15000 circles as you said.
Command: TESTVARIOUSTYPECHECK
Time elapsed in TypeCheckApproach1 for 15000 circles: 1669.2029
Time elapsed in TypeCheckApproach2 for 15000 circles: 1388.4025
Time elapsed in TypeCheckApproach3 for 15000 circles: 15.6
Time elapsed in TypeCheckApproach4 for 15000 circles: 31.2001
Also surprisingly, the approach #3 was not seemed to be affected by the entity amount increase at all, or just a little bit, but the first two were greated impacted. We really found something out then. Thanks for your contribution.
Clearly some concepts need to be clarified further and another article seems necessary to summurize all these.
Posted by: Spiderinnet1 | 04/21/2012 at 05:43 PM
Firstly, Great blog. !
I've found I get more consistant results using the Stopwatch class
ie
Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
Stopwatch sw = Stopwatch.StartNew();
int count = TypeCheckApproach1().Count;
sw.Stop();
ed.WriteMessage("Time elapsed in TypeCheckApproach1 for {0} circles: {1} ms.\n",
count, sw.Elapsed.TotalMilliseconds);
sw = Stopwatch.StartNew();
count = TypeCheckApproach2().Count;
sw.Stop();
ed.WriteMessage("Time elapsed in TypeCheckApproach2 for {0} circles: {1} ms.\n",
count, sw.Elapsed.TotalMilliseconds);
... etc
Regards
Kerry
Posted by: Kerry Brown | 04/21/2012 at 09:46 PM
What might be the consistent or inconsistent results then? Would you mind elaborating a bit?
Thanks for your kind words for the blog.
Posted by: Spiderinnet1 | 04/22/2012 at 02:48 AM
Using the DateTime I couldn't get a time below 15.6 (except for 0.0) :-)
These are typical results after first run for jitting ...
Command: TESTVARIOUSTYPECHECK
Using DateTime Class
-------------------------------
Time elapsed in TypeCheckApproach1 for 10000 circles: 140.4002
Time elapsed in TypeCheckApproach2 for 10000 circles: 156.0003
Time elapsed in TypeCheckApproach3 for 10000 circles: 452.4008
Time elapsed in TypeCheckApproach3A for 10000 circles: 0
Time elapsed in TypeCheckApproach4 for 10000 circles: 15.6
-------------------------------
Time elapsed in TypeCheckApproach1 for 10000 circles: 171.6003
Time elapsed in TypeCheckApproach2 for 10000 circles: 140.4003
Time elapsed in TypeCheckApproach3 for 10000 circles: 452.4008
Time elapsed in TypeCheckApproach3A for 10000 circles: 0
Time elapsed in TypeCheckApproach4 for 10000 circles: 0
-------------------------------
Time elapsed in TypeCheckApproach1 for 10000 circles: 156.0003
Time elapsed in TypeCheckApproach2 for 10000 circles: 171.6003
Time elapsed in TypeCheckApproach3 for 10000 circles: 436.8008
Time elapsed in TypeCheckApproach3A for 10000 circles: 15.6
Time elapsed in TypeCheckApproach4 for 10000 circles: 0
-------------------------------
+++++++++++++++++++++++++++++
Using StopWatch Class
-------------------------------
Time elapsed in TypeCheckApproach1 for 10000 circles: 116.2754 ms.
Time elapsed in TypeCheckApproach2 for 10000 circles: 104.6556 ms.
Time elapsed in TypeCheckApproach3 for 10000 circles: 449.2357 ms.
Time elapsed in TypeCheckApproach3A for 10000 circles: 6.9707 ms.
Time elapsed in TypeCheckApproach4 for 10000 circles: 7.9814 ms.
-------------------------------
Time elapsed in TypeCheckApproach1 for 10000 circles: 105.9845 ms.
Time elapsed in TypeCheckApproach2 for 10000 circles: 105.6243 ms.
Time elapsed in TypeCheckApproach3 for 10000 circles: 447.5321 ms.
Time elapsed in TypeCheckApproach3A for 10000 circles: 6.8398 ms.
Time elapsed in TypeCheckApproach4 for 10000 circles: 8.2846 ms.
-------------------------------
Time elapsed in TypeCheckApproach1 for 10000 circles: 99.4221 ms.
Time elapsed in TypeCheckApproach2 for 10000 circles: 99.3179 ms.
Time elapsed in TypeCheckApproach3 for 10000 circles: 448.1817 ms.
Time elapsed in TypeCheckApproach3A for 10000 circles: 6.7455 ms.
Time elapsed in TypeCheckApproach4 for 10000 circles: 8.0213 ms.
-------------------------------
Stopwatch seems to record the lesser times that DateTime will not.
Regards
Kerry
Posted by: Kerry Brown | 04/22/2012 at 07:03 AM
Thanks for the code snippets and the output data. Now things are all clear. The data spoke for itself again!
I reproduced the same behavior here. Obviously, there are some unknown stuffs about the .NET TimeSpan class. Using the StopWatch and its Elapsed.TotalMilliseconds provides more precise and consistent results. By the way, the Approach A idea is nice too.
In terms of the results, to make the data more objective, I created some other entities such as 5000 lines and points besides the original 5000 circles in the same drawing on my side this time and still got the same pattern as yours. Here is one:
Command: TESTVARIOUSTYPECHECKA
Time elapsed in TypeCheckApproach1 for 5000 circles: 118.2025
Time elapsed in TypeCheckApproach2 for 5000 circles: 104.0364
Time elapsed in TypeCheckApproach2A for 5000 circles: 102.4317
Time elapsed in TypeCheckApproach3 for 5000 circles: 1436.8256
Time elapsed in TypeCheckApproach3A for 5000 circles: 19.8097
Time elapsed in TypeCheckApproach4 for 5000 circles: 22.0924
Another post will summarize all these soon.
Posted by: Spiderinnet1 | 04/23/2012 at 12:47 AM