异常处理
AutoCAD 和 Revit 的 API 可能在各种情况下抛出异常:对象被释放、事务未正确打开、元素被删除等。良好的异常处理是稳定插件的基石。
try-catch-finally
try
{
// 可能抛异常的代码
var line = new Line(startPoint, endPoint);
doc.AddToModelSpace(line);
}
catch (Autodesk.AutoCAD.Runtime.Exception ex)
{
// 处理特定异常
doc.Editor.WriteMessage($"AutoCAD 异常:{ex.Message}");
}
catch (Exception ex) when (ex is not OutOfMemoryException)
{
// 带过滤器的 catch(C# 6+)
Console.WriteLine($"通用异常:{ex.Message}");
}
finally
{
// 无论如何都执行(资源释放等)
// 通常用 using 替代手动 finally
}常见异常类型
| 异常 | 触发场景 |
|---|---|
NullReferenceException |
对 null 对象调用方法/属性 |
InvalidOperationException |
对象状态不正确(如 foreach 中修改集合) |
ArgumentException |
参数无效 |
ArgumentNullException |
传入 null 参数 |
ObjectDisposedException |
访问已释放的对象 |
IOException |
文件操作失败 |
UnauthorizedAccessException |
权限不足 |
OperationCanceledException |
异步操作被取消 |
AggregateException |
多个异步异常包装 |
AutoCAD 特有异常
| 异常 | 触发场景 |
|---|---|
Autodesk.AutoCAD.Runtime.Exception |
CAD 运行时错误(如对象不存在) |
AccessViolationException |
访问未托管内存(通常在事务外操作对象时发生) |
🚨 AutoCAD 中销毁已提交事务后的对象 ID 再次使用时,产生的是
AccessViolationException或未定义行为,而非友好的 .NET 异常——这很难排查。
Revit 特有异常
| 异常 | 触发场景 |
|---|---|
Autodesk.Revit.Exceptions.InvalidOperationException |
文档只读、无活动事务、再生期间操作等 |
Autodesk.Revit.Exceptions.ArgumentException |
参数无效 |
Autodesk.Revit.Exceptions.ApplicationException |
Revit 内部错误 |
异常过滤器
// catch 多个具体异常类型(从具体到通用)
try
{
ProcessEntity(entity);
}
catch (ArgumentException ex)
{
Console.WriteLine($"参数无效:{ex.ParamName}");
}
catch (InvalidOperationException ex)
{
Console.WriteLine($"状态无效:{ex.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"未预期错误:{ex}");
}
// 异常过滤器(C# 6+):根据条件决定是否捕获
try
{
ProcessEntity(entity);
}
catch (Exception ex) when (ex is not OutOfMemoryException)
{
Log.Error(ex, "可恢复的错误");
// OutOfMemoryException 不会被捕获,向上传播
}自定义异常
public class AddinException : Exception
{
public string CommandName { get; }
public AddinException(string commandName, string message)
: base(message)
{
CommandName = commandName;
}
public AddinException(string commandName, string message, Exception inner)
: base(message, inner)
{
CommandName = commandName;
}
}
// 使用
throw new AddinException("DRAWLINE", "无法在锁定图层上绘制直线");using 语句与资源管理
多数情况下 using 比 try-finally 更简洁:
// ❌ 冗长的 try-finally
StreamWriter? writer = null;
try
{
writer = new StreamWriter(filePath);
writer.WriteLine("数据");
}
finally
{
writer?.Dispose();
}
// ✅ using 语句
using var writer = new StreamWriter(filePath);
writer.WriteLine("数据");
// 多个资源
using var fs = new FileStream(path, FileMode.Open);
using var reader = new StreamReader(fs);
string content = reader.ReadToEnd();AutoCAD/Revit 开发中的异常处理最佳实践
1. 命令入口处必须 try-catch
// AutoCAD 命令
[CommandMethod("MyCommand")]
public void MyCommand()
{
try
{
ExecuteCore();
}
catch (System.Exception ex)
{
Application.DocumentManager.MdiActiveDocument
.Editor.WriteMessage($"\n错误:{ex.Message}");
}
}
// Revit 命令
public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
{
try
{
return ExecuteCore(commandData);
}
catch (Autodesk.Revit.Exceptions.OperationCanceledException)
{
return Result.Cancelled;
}
catch (System.Exception ex)
{
message = $"执行失败:{ex.Message}";
return Result.Failed;
}
}2. 事务必须正确释放
// AutoCAD 事务
using (var tr = db.TransactionManager.StartTransaction())
{
var line = tr.GetObject(id, OpenMode.ForRead) as Line;
// ...
tr.Commit(); // 不 Commit 则自动回滚
} // Dispose 自动释放
// Revit 事务
using (var tx = new Transaction(doc, "修改墙体"))
{
tx.Start();
wall.get_Parameter(BuiltInParameter.ALL_MODEL_INSTANCE_COMMENTS).Set("备注");
tx.Commit(); // 必须显式提交或回滚
}🚨 AutoCAD 的 Transaction 在未
Commit()的情况下离开using块会自动回滚,但显式Commit()是最佳实践。Revit 的 Transaction 在Dispose时如果未提交会抛异常。
3. 对象已释放的陷阱
// ❌ 错误:在事务外访问 DBObject
Line line;
using (var tr = db.TransactionManager.StartTransaction())
{
line = tr.GetObject(id, OpenMode.ForRead) as Line;
tr.Commit();
}
double len = line.Length; // 🚨 对象已被释放!崩溃!💡 解决:在事务内读取需要的数据并保存为内存值(
double、string),或使用GetObject(id, OpenMode.ForRead, true)使对象在事务结束后存活(慎用)。