明经CAD社区

 找回密码
 注册

QQ登录

只需一步,快速开始

搜索
查看: 2768|回复: 7

Dynamic .NET

[复制链接]
发表于 2012-7-21 14:54:54 | 显示全部楼层 |阅读模式
本帖最后由 雪山飞狐_lzh 于 2012-7-21 15:00 编辑

Dynamic .NET in AutoCAD 2013
Another really interesting, developer-oriented feature in AutoCAD 2013 is something we’ve been calling “Dynamic .NET”. I don’t know whether that’s official branding, or not – I suspect not – but it’s the moniker we’ve been using to describe this capability internally and to ADN members.
The capability is based on an addition to .NET in version 4.0: to complement (or perhaps just as part of) the integration of the Dynamic Language Runtime into the core .NET Framework, various interfaces – including IDynamicMetaObjectProvider – were provided to developers to let their objects participate in “dynamic” operations.
Providers of APIs therefore now have the option to make certain classes dynamic – for instance, having them implement the IDynamicMetaObjectProvider interface – which means they can effectively be scripted, without binding properties and methods at compile-time. This implements a concept known as duck typing, and is at the core of many scripting language implementations. You may also hear it referred to as late binding: take a look at this excellent post by Eric Lippert if you’d like to get a better understanding of what that term means.
So what have we actually done with AutoCAD 2013? We have made Autodesk.AutoCAD.DatabaseServices.ObjectId support dynamic operations: you can declare an ObjectId using the dynamic keyword in C#, and you can then choose to access any of the properties or methods exposed by the object possessing that ID directly from the ObjectId itself. The ObjectId then takes care of the open & close operations implicitly, so there’s no need to mess around with transactions, etc.
If you’re interested in more background to this, I suggest taking a look at Albert Szilvasy’s class from AU 2009, where he gave us a hint of what was to come (although it was far from being a certainty, at that time – we have since found it to be a popular option via our annual API wishlist surveys).
OK, so what does this mean, in practice? Here’s some statically-typed C# code that iterates through the layer table and prefixes all layers (other than layer “0”) with a string:
public static void ChangeLayerNames()
{
  Database db = HostApplicationServices.WorkingDatabase;

  using (Transaction tr = db.TransactionManager.StartTransaction())
  {
    LayerTable lt =
      (LayerTable)tr.GetObject(
        db.LayerTableId, OpenMode.ForRead
      );

    foreach (ObjectId ltrId in lt)
    {
      LayerTableRecord ltr =
        (LayerTableRecord)tr.GetObject(ltrId, OpenMode.ForRead);

      if (ltr.Name != "0")
      {
        ltr.UpgradeOpen();
        ltr.Name = "First Floor " + ltr.Name;
      }
    }
    tr.Commit();
  }
}
Here’s how the code could look if using dynamic types. I’ve done my best not to exaggerate the differences in numbers of lines by using different conventions in the two implementations, but I did have to add more line breaks in the above code to have it fit the width of this blog.
public static void ChangeLayerNamesDynamically()
{
  dynamic layers =
    HostApplicationServices.WorkingDatabase.LayerTableId;

  foreach (dynamic l in layers)
    if (l.Name != "0")
      l.Name = "First Floor " + l.Name;
}
Something that needs to be stressed: as the properties and methods are effectively being determined and then accessed/called at run-time, there is no IntelliSense available when coding dynamically (something Python and Ruby developers are used to, but we statically-typed .NET developers are probably less so). It would not be impossible to implement, but it would be very difficult: aside from types being inferred at the time code is being written (I’ll use the term develop-time, although I haven’t seen it used before), there would need to be some work done to effectively populate the list of properties & methods, which would typically mean opening the object and reflecting over its protocol… not at all straightforward to implement, as far as I can tell, especially when you don’t actually have AutoCAD running to help with accessing the objects.
A lack of IntelliSense does mean this style of coding it likely to be less accessible to novice coders, unless a more integrated REPL environment becomes available (as this is something that tends to balance out the lack of other develop-time tooling when scripting – just as people have found with the ability to execute fragments of AutoLISP at AutoCAD’s command-line, over the years).
I can see a couple of ways a REPL might end up being implemented. One would be to host the DLR and (say) the IronPython scripting engine (just as Albert showed during his above-mentioned AU class, as I did at my own AU 2009 class, and we’ve seen previously on this blog). Another would be to wait for the fruits of the future .NET Framework feature codenamed Roslyn, which enables Compiler as a Service for .NET  code, as this seems it would be a likely way to execute C# and VB.NET in a more dynamic way.
But that’s all for the future, and is very much my own speculation, at this stage.
One question that has come up in relation to “Dynamic .NET” in AutoCAD is around performance: as we’re effectively opening and closing an object every time we execute a method (and we may actually open and close an object twice – once for read, once for write – when we read one of its properties, modify it and store it back), isn’t there a performance impact? I admit I haven’t performed extensive benchmarking of this, and there is likely to be some overhead if dealing with lots of properties and methods. But in terms of getting something working quickly – which you can then tune for performance later, as needed – this is likely to be a valuable tool for developers, irrespective of the performance impact (which I’d hope to be modest).
And it really comes into its own when used in combination with LINQ. Here’s some code that Stephen Preston put together to test out some of the possibilities with LINQ (I’ve included the CLN and CLN2 commands – shown above – for completeness).
  1. using Autodesk.AutoCAD.Runtime;
  2. using Autodesk.AutoCAD.ApplicationServices;
  3. using Autodesk.AutoCAD.DatabaseServices;
  4. using Autodesk.AutoCAD.Geometry;
  5. using Autodesk.AutoCAD.EditorInput;
  6. using System.Collections.Generic;
  7. using System.Linq;

  8. namespace DynamicTyping
  9. {
  10.   public class Commands
  11.   {
  12.     [CommandMethod("CLN")]
  13.     public static void ChangeLayerNames()
  14.     {
  15.       Database db = HostApplicationServices.WorkingDatabase;

  16.       using (
  17.         Transaction tr = db.TransactionManager.StartTransaction()
  18.       )
  19.       {
  20.         LayerTable lt =
  21.           (LayerTable)tr.GetObject(
  22.             db.LayerTableId, OpenMode.ForRead
  23.           );

  24.         foreach (ObjectId ltrId in lt)
  25.         {
  26.           LayerTableRecord ltr =
  27.             (LayerTableRecord)tr.GetObject(ltrId, OpenMode.ForRead);

  28.           if (ltr.Name != "0")
  29.           {
  30.             ltr.UpgradeOpen();
  31.             ltr.Name = "First Floor " + ltr.Name;
  32.           }
  33.         }

  34.         tr.Commit();
  35.       }
  36.     }

  37.     [CommandMethod("CLN2")]
  38.     public static void ChangeLayerNamesDynamically()
  39.     {
  40.       dynamic layers =
  41.         HostApplicationServices.WorkingDatabase.LayerTableId;

  42.       foreach (dynamic l in layers)
  43.         if (l.Name != "0")
  44.           l.Name = "First Floor " + l.Name;
  45.     }

  46.     // Adds a custom dictionary in the extension dictionary of
  47.     // selected objects. Uses dynamic capabilities, but not LINQ.

  48.     [CommandMethod("AddMyDict")]
  49.     public void AddMyDict()
  50.     {
  51.       Document doc = Application.DocumentManager.MdiActiveDocument;
  52.       Database db = doc.Database;
  53.       Editor ed = doc.Editor;

  54.       PromptSelectionResult res = ed.GetSelection();

  55.       if (res.Status != PromptStatus.OK)
  56.         return;

  57.       foreach (dynamic ent in res.Value.GetObjectIds())
  58.       {
  59.         dynamic dictId = ent.ExtensionDictionary;
  60.         if (dictId == ObjectId.Null)
  61.         {
  62.           ent.CreateExtensionDictionary();
  63.           dictId = ent.ExtensionDictionary();
  64.         }
  65.         if (!dictId.Contains("MyDict"))
  66.           dictId.SetAt("MyDict", new DBDictionary());
  67.       }
  68.     }

  69.     // Adds a custom dictionary in the extension dictionary of
  70.     // selected objects, but this time using dynamic capabilities
  71.     // with LINQ. Conceptually simpler, but with some performance
  72.     // overhead, as we're using two queries: one to get entities
  73.     // without extension dictionaries (and then add them) and the
  74.     // other to get entities with extension dictionaries.

  75.     [CommandMethod("AddMyDict2")]
  76.     public void AddMyDict2()
  77.     {
  78.       PromptSelectionResult res =
  79.         Application.DocumentManager.MdiActiveDocument.Editor.
  80.           GetSelection();

  81.       if (res.Status != PromptStatus.OK)
  82.         return;

  83.       // Query for ents in selset without ExtensionDictionaries

  84.       var noExtDicts =
  85.         from ent in res.Value.GetObjectIds().Cast<dynamic>()
  86.           where ent.ExtensionDictionary == ObjectId.Null
  87.           select ent;

  88.       // Add extension dictionaries

  89.       foreach (dynamic ent in noExtDicts)
  90.         ent.CreateExtensionDictionary();

  91.       // Now we've added the ext dicts, we add our dict to each

  92.       var noMyDicts =
  93.         from ent in res.Value.GetObjectIds().Cast<dynamic>()
  94.           where !ent.ExtensionDictionary.Contains("MyDict")
  95.           select ent.ExtensionDictionary;

  96.       foreach (dynamic dict in noMyDicts)
  97.         dict.SetAt("MyDict", new DBDictionary());
  98.     }

  99.     // Access various bits of information using LINQ

  100.     [CommandMethod("IUL")]
  101.     public void InfoUsingLINQ()
  102.     {
  103.       Document doc = Application.DocumentManager.MdiActiveDocument;
  104.       Database db = doc.Database;
  105.       Editor ed = doc.Editor;

  106.       dynamic bt = db.BlockTableId;

  107.       // Dynamic .NET loop iteration

  108.       ed.WriteMessage("\n*** BlockTableRecords in this DWG ***");
  109.       foreach (dynamic btr in bt)
  110.         ed.WriteMessage("\n" + btr.Name);

  111.       // LINQ query - returns startpoints of all lines

  112.       ed.WriteMessage(
  113.         "\n\n*** StartPoints of Lines in ModelSpace ***"
  114.       );

  115.       dynamic ms = SymbolUtilityServices.GetBlockModelSpaceId(db);
  116.       var lineStartPoints =
  117.         from ent in (IEnumerable<dynamic>)ms
  118.           where ent.IsKindOf(typeof(Line))
  119.           select ent.StartPoint;

  120.       foreach (Point3d start in lineStartPoints)
  121.         ed.WriteMessage("\n" + start.ToString());

  122.       // LINQ query - all entities on layer '0'

  123.       ed.WriteMessage("\n\n*** Entities on Layer 0 ***");

  124.       var entsOnLayer0 =
  125.         from ent in (IEnumerable<dynamic>)ms
  126.           where ent.Layer == "0"
  127.           select ent;

  128.       foreach (dynamic e in entsOnLayer0)
  129.         ed.WriteMessage(
  130.           "\nHandle=" + e.Handle.ToString() + ", ObjectId=" +
  131.           ((ObjectId)e).ToString() + ", Class=" + e.ToString()
  132.         );
  133.       ed.WriteMessage("\n\n");

  134.       // Using LINQ with selection sets

  135.       PromptSelectionResult res = ed.GetSelection();
  136.       if (res.Status != PromptStatus.OK)
  137.         return;

  138.       // Select all entities in selection set that have an object
  139.       // called "MyDict" in their extension dictionary

  140.       var extDicts =
  141.         from ent in res.Value.GetObjectIds().Cast<dynamic>()
  142.           where ent.ExtensionDictionary != ObjectId.Null &&
  143.             ent.ExtensionDictionary.Contains("MyDict")
  144.           select ent.ExtensionDictionary.Item("MyDict");

  145.       // Erase our dictionary

  146.       foreach (dynamic myDict in extDicts)
  147.         myDict.Erase();
  148.     }
  149.   }
  150. }

You’ll see that certain operations become very straightforward when combining Dynamic .NET and LINQ. This could well be the “sweet spot” of the dynamic capability – it certainly makes LINQ a more natural choice when accessing various types of data in AutoCAD drawings.
That’s it for today’s post. Next time we’ll summarize the remaining API enhancements coming in AutoCAD 2013, before looking at the migration steps needed for .NET applications.

发表于 2012-7-21 18:09:42 | 显示全部楼层
AutoCAD.NET API对.NET框架的动态语言运行时的支持
发表于 2012-7-21 22:35:17 | 显示全部楼层
飞狐老大的帖子,一定要支持!
发表于 2012-7-22 00:10:38 | 显示全部楼层
本帖最后由 河伯 于 2012-7-22 00:11 编辑

ObjectId对动态语言的支持,就是VB.NET早先的后期绑定。
实际使用起来并不方便,IDE没有智能提示,属性方法都要是手动输入。
本来DBObject可以直接实现的功能,非要绕弯用ObjectId实现,好像为动态而动态,不知道Autodesk怎么想的。
 楼主| 发表于 2012-7-22 09:40:54 | 显示全部楼层
只是看起来很Cool而已 呵呵
发表于 2012-10-15 01:13:36 | 显示全部楼层
很好很强大
发表于 2012-10-15 21:12:47 | 显示全部楼层
飞狐哥哥又回来了!
发表于 2012-10-17 17:24:24 | 显示全部楼层
为什么谈VB.NET的这么少呢?
您需要登录后才可以回帖 登录 | 注册

本版积分规则

小黑屋|手机版|CAD论坛|CAD教程|CAD下载|联系我们|关于明经|明经通道 ( 粤ICP备05003914号 )  
©2000-2023 明经通道 版权所有 本站代码,在未取得本站及作者授权的情况下,不得用于商业用途

GMT+8, 2024-11-25 16:47 , Processed in 0.156063 second(s), 23 queries , Gzip On.

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表