Author Topic: AU 2005 code - Part 8 "Database events"  (Read 13418 times)

0 Members and 1 Guest are viewing this topic.

Bobby C. Jones

  • Swamp Rat
  • Posts: 516
  • Cry havoc and let loose the dogs of war.
AU 2005 code - Part 8 "Database events"
« on: December 28, 2005, 03:12:06 PM »
Bonus!  I wrote this example just in case the first seven didn't take up the entire class time.  However, the first seven took up *more* than enough time and I never got to show this one.  It watches for lines being added to the active database and changes them to a preset layer.  In hind site I should probably have bumped something else for this one, *sigh*.

Note:  This code calls the CreateLayer method from the class in example 7.

Code: [Select]

using System;

using AU2005.AcadApi;

using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.DatabaseServices;
using AcadApp = Autodesk.AutoCAD.ApplicationServices.Application;

namespace AU2005.AcadApi
{
//Code not in the handout!
public class DatabaseEventExample
{
private const string NEW_LINE_LAYER_NAME = "NewLines";


[CommandMethod("CaptureLines")]
public static void CaptureLines()
{
Database activeDatabase = AcadApp.DocumentManager.MdiActiveDocument.Database;

DatabaseExamples.CreateLayer(NEW_LINE_LAYER_NAME, activeDatabase);

activeDatabase.ObjectAppended += new ObjectEventHandler(OnObjectAppended);
}


[CommandMethod("StopCaptureLines")]
public static void StopCaptureLines()
{
Database activeDatabase = AcadApp.DocumentManager.MdiActiveDocument.Database;

activeDatabase.ObjectAppended -= new ObjectEventHandler(OnObjectAppended);
}


private static void OnObjectAppended(object sender, ObjectEventArgs e)
{
if (e.DBObject is Line)
{
Line newLine = e.DBObject as Line;

newLine.UpgradeOpen();

if (newLine.IsWriteEnabled)
{
newLine.Layer = NEW_LINE_LAYER_NAME;
}
}
}
}
}

Bobby C. Jones

Glenn R

  • Guest
Re: AU 2005 code - Part 8 "Database events"
« Reply #1 on: December 29, 2005, 09:12:08 AM »
Bobby,

First off, thanks for posting your classes from AU - I'm sure a lot of people will benefit from it, as will the board.

Secondly, for anybody following along, ponder the code below:
Quote
Code: [Select]
private static void OnObjectAppended(object sender, ObjectEventArgs e)
{
if (e.DBObject is Line)
{
Line newLine = e.DBObject as Line;

newLine.UpgradeOpen();

if (newLine.IsWriteEnabled)
{
newLine.Layer = NEW_LINE_LAYER_NAME;
}
}
}

This is definately doing one cast ( the 'IS' statement) and potentially doing 2 casts (the 'AS' statement).
One thing I've learned from doing C++ and ARX programming is performance.

Let me make myself perfectly clear here: You should always write code that perfoms the best it can, irrrespective of the fact
that the .NET CLR will clean up behind you.

Another way to write the above function would be like this:
Code: [Select]
private static void OnObjectAppended(object sender, ObjectEventArgs e) {
Line newLine = e.DBObject as Line;

if (newLine == null)
        return;

newLine.UpgradeOpen();

if (newLine.IsWriteEnabled)
newLine.Layer = NEW_LINE_LAYER_NAME;
}

Using the 'AS' keyword in this context is only a single cast and it's safe.
If the cast fails, it will return null, as opposed to a direct cast, which will
throw an exception if it fails.

This is by no means meant as a dig at anybody, I'm just trying to help everybody learn.

Merry Xmas and a Happy New Year.

Cheers,
Glenn.

Bobby C. Jones

  • Swamp Rat
  • Posts: 516
  • Cry havoc and let loose the dogs of war.
Re: AU 2005 code - Part 8 "Database events"
« Reply #2 on: December 29, 2005, 10:30:10 AM »
Bobby,

First off, thanks for posting your classes from AU - I'm sure a lot of people will benefit from it, as will the board.

Hi Glenn,
You're welcome.  And I only hope that someone, anyone, will benefit half as much from it as I have.

Let me make myself perfectly clear here: You should always write code that perfoms the best it can, irrrespective of the fact
that the .NET CLR will clean up behind you.



This is by no means meant as a dig at anybody, I'm just trying to help everybody learn.

First, no digs detected.  I enjoy discussing differing views without digging into each other.

Second, I respectfully disagree.  I did another class at AU this year and its main goal was to show the exact opposite of what you are saying.  I code with three goals in mind:

Goal #1 - Write code that works.  If it doesn't work as desired by the end user then I get fired.

Goal #2 - Write code that is "right".  I like to say that this is really goal #1.5.

Goal #3 - Write code that is fast.  And I also like to say that this is a distant 3rd behind the first two goals.

The majority of the class focused on Goal #2, and there are a lot of things that make code "right".  If I have to sum it up, "right" code screams its intent at the top of its lungs.  To me, testing with the is keyword and then casting again is much clearer in its intent than using the as keyword and testing for null.  Creating "right" code trumps creating fast code and I will aways write "right" code first.

Now, if testing proves that this function, and in particular this double casting, is a bottleneck then, and only then, I would look at tuning it.  This is probably what I would try first:

**Untested code below**
Code: [Select]

      private static void OnObjectAppended(object sender, ObjectEventArgs e)
      {
         if (IsLine(e.Object))
         {
            newLine.UpgradeOpen();

            if (newLine.IsWriteEnabled)
            {
               newLine.Layer = NEW_LINE_LAYER_NAME;
            }
         }
      }

      private static bool IsLine(object appendedObject)
      {
         Line newLine = appendedObject as Line;
         return (newLine != null);
      }

Bobby C. Jones

Bobby C. Jones

  • Swamp Rat
  • Posts: 516
  • Cry havoc and let loose the dogs of war.
Re: AU 2005 code - Part 8 "Database events"
« Reply #3 on: December 29, 2005, 11:13:19 AM »

Now, if testing proves that this function, and in particular this double casting, is a bottleneck then, and only then, I would look at tuning it.  This is probably what I would try first:

**Untested code below**
Code: [Select]

      private static void OnObjectAppended(object sender, ObjectEventArgs e)
      {
         if (IsLine(e.Object))
         {
            newLine.UpgradeOpen();

            if (newLine.IsWriteEnabled)
            {
               newLine.Layer = NEW_LINE_LAYER_NAME;
            }
         }
      }

      private static bool IsLine(object appendedObject)
      {
         Line newLine = appendedObject as Line;
         return (newLine != null);
      }



Of course that doesn't work, doh!  You would need to preform the cast in the OnObjectAppended() method and just test for a null value in the IsLine() method. 
Bobby C. Jones

Glenn R

  • Guest
Re: AU 2005 code - Part 8 "Database events"
« Reply #4 on: December 29, 2005, 06:47:32 PM »
An Xmas debate - this should be fun :)

Observe the following:
Code: [Select]
.method private hidebysig static void  OnObjectAppended(object sender,
                                                        class [acdbmgd]Autodesk.AutoCAD.DatabaseServices.ObjectEventArgs e) cil managed
{
  // Code size       51 (0x33)
  .maxstack  2
  .locals init ([0] class [acdbmgd]Autodesk.AutoCAD.DatabaseServices.Line newLine)
  IL_0000:  ldarg.1
  IL_0001:  callvirt   instance class [acdbmgd]Autodesk.AutoCAD.DatabaseServices.DBObject [acdbmgd]Autodesk.AutoCAD.DatabaseServices.ObjectEventArgs::get_DBObject()
  IL_0006:  isinst     [acdbmgd]Autodesk.AutoCAD.DatabaseServices.Line
  IL_000b:  brfalse.s  IL_0032
  IL_000d:  ldarg.1
  IL_000e:  callvirt   instance class [acdbmgd]Autodesk.AutoCAD.DatabaseServices.DBObject [acdbmgd]Autodesk.AutoCAD.DatabaseServices.ObjectEventArgs::get_DBObject()
  IL_0013:  isinst     [acdbmgd]Autodesk.AutoCAD.DatabaseServices.Line
  IL_0018:  stloc.0
  IL_0019:  ldloc.0
  IL_001a:  callvirt   instance void [acdbmgd]Autodesk.AutoCAD.DatabaseServices.DBObject::UpgradeOpen()
  IL_001f:  ldloc.0
  IL_0020:  callvirt   instance bool [acdbmgd]Autodesk.AutoCAD.DatabaseServices.DBObject::get_IsWriteEnabled()
  IL_0025:  brfalse.s  IL_0032
  IL_0027:  ldloc.0
  IL_0028:  ldstr      "NewLines"
  IL_002d:  callvirt   instance void [acdbmgd]Autodesk.AutoCAD.DatabaseServices.Entity::set_Layer(string)
  IL_0032:  ret
} // end of method DatabaseEventExample::OnObjectAppended


.method private hidebysig static void  OnObjectAppended(object sender,
                                                        class [acdbmgd]Autodesk.AutoCAD.DatabaseServices.ObjectEventArgs e) cil managed
{
  // Code size       49 (0x31)
  .maxstack  2
  .locals init ([0] class [acdbmgd]Autodesk.AutoCAD.DatabaseServices.Line newLine)
  IL_0000:  ldarg.1
  IL_0001:  callvirt   instance class [acdbmgd]Autodesk.AutoCAD.DatabaseServices.DBObject [acdbmgd]Autodesk.AutoCAD.DatabaseServices.ObjectEventArgs::get_DBObject()
  IL_0006:  isinst     [acdbmgd]Autodesk.AutoCAD.DatabaseServices.Line
  IL_000b:  stloc.0
  IL_000c:  ldloc.0
  IL_000d:  ldnull
  IL_000e:  call       bool [acdbmgd]Autodesk.AutoCAD.Runtime.DisposableWrapper::op_Equality(class [acdbmgd]Autodesk.AutoCAD.Runtime.DisposableWrapper,
                                                                                             class [acdbmgd]Autodesk.AutoCAD.Runtime.DisposableWrapper)
  IL_0013:  brfalse.s  IL_0017
  IL_0015:  br.s       IL_0030
  IL_0017:  ldloc.0
  IL_0018:  callvirt   instance void [acdbmgd]Autodesk.AutoCAD.DatabaseServices.DBObject::UpgradeOpen()
  IL_001d:  ldloc.0
  IL_001e:  callvirt   instance bool [acdbmgd]Autodesk.AutoCAD.DatabaseServices.DBObject::get_IsWriteEnabled()
  IL_0023:  brfalse.s  IL_0030
  IL_0025:  ldloc.0
  IL_0026:  ldstr      "NewLines"
  IL_002b:  callvirt   instance void [acdbmgd]Autodesk.AutoCAD.DatabaseServices.Entity::set_Layer(string)
  IL_0030:  ret
} // end of method DatabaseEventExample::OnObjectAppended

Bobby's first function is first, my way is second. Pay particular attention to 'isinst' in the MSIL code above...this is what I was talking about with the double casting.

My first reaction on seeing your statement about writing 'right' code (as in it reads well) was to say "use VB - it's verbose for a reason". However, I can see your point to an extent.
I would put this to you though - do it right and it will be fast...the 2 go hand in hand in my opinion.

Also, you replied that casting and then checking for null was losing the clarity of the code, but you then stick in an unnecessary function, call it, and in that function you check for null....what's up with that?


Just a bit of friendly banter Bobby...I'm sure somebody will come along and bite my head off.

cheers,
Glenn.

Kerry

  • Mesozoic relic
  • Seagull
  • Posts: 11654
  • class keyThumper<T>:ILazy<T>
Re: AU 2005 code - Part 8 "Database events"
« Reply #5 on: December 29, 2005, 07:14:36 PM »
I dont have the wherewithall to join in a full debate on this, but I recalled reading this ..
from Jesse Liberty
Programming C#, 4nd Edition Feb2005
Quote
8.2.4 The is Operator Versus the as Operator
If your design pattern is to test the object to see if it is of the type you need, and if so you will
immediately cast it, the as operator is more efficient. At times, however, you might want to
test the type of an operator but not cast it immediately. Perhaps you want to test it but not cast
it at all; you simply want to add it to a list if it fulfills the right interface. In that case, the is
operator will be a better choice.

I recall hearing a similar recommendation from one of the Webcasts.

Doesn't the as operator actually combine both the is and cast .. not that that matters for this debate I s'pose.

kdub, kdub_nz in other timelines.
Perfection is not optional.
Everything will work just as you expect it to, unless your expectations are incorrect.
Discipline: None at all.

Kerry

  • Mesozoic relic
  • Seagull
  • Posts: 11654
  • class keyThumper<T>:ILazy<T>
Re: AU 2005 code - Part 8 "Database events"
« Reply #6 on: December 29, 2005, 08:25:20 PM »
Bobby, I haven't read the class material you mention, so passing comment is difficult.

Keeping in mind that this whole topic is like a bag of snakes, and opinions will depend on who you talk to ..  :

I'm having trouble ranking "transparent intent" higher than performance. I read this as having to "dumb-down" my code  somehow so that it can be understood by someone who shouldn't be reading it in the first place. .. thats what an outline is for.
C# is a remarkably transparent and straightforward language, in my opinion. I dont see how it can be structured to be read more easily without affecting performance.


One thing I have noted is that my code improves the more I write. Perhaps the solution to the quandry is tied to that somehow.
... I s'pose the debate about a definition of 'improves' is going to continue as long as there are programmers to debate it.

added :
I wonder if goals 1 & 2 are actually goals as such .. aren't they actually part of the prerequisites for any code, by definition.
« Last Edit: December 29, 2005, 08:34:58 PM by Kerry Brown »
kdub, kdub_nz in other timelines.
Perfection is not optional.
Everything will work just as you expect it to, unless your expectations are incorrect.
Discipline: None at all.

Glenn R

  • Guest
Re: AU 2005 code - Part 8 "Database events"
« Reply #7 on: December 29, 2005, 08:49:01 PM »
Kerry, you're correct. The as operator does combine the 2 operations into one...which was my point.

Bobby C. Jones

  • Swamp Rat
  • Posts: 516
  • Cry havoc and let loose the dogs of war.
Re: AU 2005 code - Part 8 "Database events"
« Reply #8 on: December 30, 2005, 10:19:54 AM »
Keeping in mind that this whole topic is like a bag of snakes, and opinions will depend on who you talk to ..  :

Kerry, Glenn, and others interested in following this topic,

I think this is a *very* important topic and I don't want it to get buried here.  Let's move it to a new thread.  And a more appropriate board.  I'll come back and post a link when I do it :-)
Bobby C. Jones

Bobby C. Jones

  • Swamp Rat
  • Posts: 516
  • Cry havoc and let loose the dogs of war.
Re: AU 2005 code - Part 8 "Database events"
« Reply #9 on: December 30, 2005, 01:14:11 PM »
Let's move it to a new thread.  And a more appropriate board.  I'll come back and post a link when I do it

Let's continue the discussion here, http://www.theswamp.org/forum/index.php?topic=8219.0
Bobby C. Jones

Glenn R

  • Guest
Re: AU 2005 code - Part 8 "Database events"
« Reply #10 on: December 30, 2005, 05:36:51 PM »
Wiiiiiiiith pleasure....<click>

Kerry

  • Mesozoic relic
  • Seagull
  • Posts: 11654
  • class keyThumper<T>:ILazy<T>
Re: AU 2005 code - Part 8 "Database events"
« Reply #11 on: December 30, 2005, 10:45:40 PM »
Bobby,
Thanks for the handler examples.
Do you have any notes prepared to accompany the code. I can imagine that event handling will be something that gets a lot of attention in the future. There are a couple of concepts tied up in the code that may require explanation for the casual reader.

Glenn,
The debate topic taught me something, thanks.

Stay well guys
Kerry

Edit : A synonym is a word you use when you can't spell the word you first thought of. Burt Bacharach (1929-)






« Last Edit: December 31, 2005, 12:14:28 AM by Kerry Brown »
kdub, kdub_nz in other timelines.
Perfection is not optional.
Everything will work just as you expect it to, unless your expectations are incorrect.
Discipline: None at all.

Glenn R

  • Guest
Re: AU 2005 code - Part 8 "Database events"
« Reply #12 on: December 31, 2005, 09:14:45 PM »
Huh? Wha? You learned something...from me...do tell...I must have missed that!  :-D :-D :-D

Kerry

  • Mesozoic relic
  • Seagull
  • Posts: 11654
  • class keyThumper<T>:ILazy<T>
Re: AU 2005 code - Part 8 "Database events"
« Reply #13 on: January 01, 2006, 01:21:12 AM »
I'm feeling too mellow to fire up the IDE at the moment, but I wondered ..

What would be the relative perfomance of using typeof or GetType or silmiar in place of the cast < implicit or explicit >

also ... what would be the most efficient way to test for multiple  object types ;
I can imagine several scenarios :
if (any of these) then (putOnLayer "A")
else if (this1) then (putOnLayer "B")
else if (this2) then (putOnLayer "another")


// //
and yes, Glenn, from you !. dont act so innocent    :wink:
« Last Edit: January 01, 2006, 01:31:39 AM by Kerry Brown »
kdub, kdub_nz in other timelines.
Perfection is not optional.
Everything will work just as you expect it to, unless your expectations are incorrect.
Discipline: None at all.

Glenn R

  • Guest
Re: AU 2005 code - Part 8 "Database events"
« Reply #14 on: January 01, 2006, 06:55:41 AM »
I wasn't acting...I'm a hack  :wink:

Hmmm....good question. I've often pondered this myself.

Here's one way:
Code: [Select]
[CommandMethod("EntType")]
public static void SwitchObjectTypesCommand() {
Editor ed = acadApp.DocumentManager.MdiActiveDocument.Editor;

PromptSelectionResult promptSelectionResult = ed.GetSelection();

if (promptSelectionResult.Status != PromptStatus.OK)
return;

using (Transaction tr = HostApplicationServices.WorkingDatabase.TransactionManager.StartTransaction()) {

SelectionSet ss = promptSelectionResult.Value;
ObjectId[] selectedIds = ss.GetObjectIds();
foreach (ObjectId entId in selectedIds) {
Entity ent = (Entity)tr.GetObject(entId, OpenMode.ForRead);

switch (ent.GetRXClass().Name) {
case "AcDbBlockReference" :
break;
case "AcDbLine" :
break;
default:
ed.WriteMessage("\nSelected entity type: {0}", ent.GetRXClass().Name);
break;
}
}

tr.Commit();
}
}

One of the differences between typeof and GetType that springs to mind would be that GetType requires an object reference whereas typeof does not.

Cheers,
Glenn.
« Last Edit: January 01, 2006, 07:51:16 AM by Glenn R »