Skip to content
C#
异常处理

异常处理

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;   // 🚨 对象已被释放!崩溃!

💡 解决:在事务内读取需要的数据并保存为内存值(doublestring),或使用 GetObject(id, OpenMode.ForRead, true) 使对象在事务结束后存活(慎用)。