Autodesk Inventor API Hacking (Transaction処理)

# 0. はじめに
少し複雑な処理をすると、Transactionを使ってオブジェクト操作履歴を1つに纏めたくなるでしょう。
しかし、例外やnullチェックでの早期リターンで、確実にAbort(もしくはEnd)出来ていないbugを内包する可能性があります。
Autodeskではこの対策としてChangeProcessorを推奨しているようですが、必ずしも使いやすいとは言えないので、何か良い方法がないか模索してみました。

# 1. usingを使おう
C#には言語仕様でusingがあります。
usingが何かを簡単に解説すると、CやC++では、ローカルオブジェクトはスコープを抜けるタイミングでデストラクターが呼ばれる保証がありますが、C#ではガーベージコレクションのタイミングまで存続するので、その欠点を補うものです。

InventorのTransaction自体がIDisposableを継承していれば話は簡単なのですが、そうではないので、wrapper classを作りました。
普段使う機能しか実装していないので、名付けてTinyTransactionです。

# 2. TinyTransactionの使い方
想定される典型的な使い方。

```C#
void foo(void)
{
    Document doc;
    bool isChanged = false;

    //
    // docの初期化など
    //
    using (var transaction = new TinyTransaction(doc, "処理の名前"))
    {
        if (something == null) {
            // この場合は、Abort()される。
            return;
        }
        try {
            //
            // いろいろとオブジェクトを操作する
            //
        } catch (Exception ex) {
            // 異常時はAbortする
            transaction.Abort();
            throw;
        }
        finally {
            if (isChanged) {
                // 変更した場合のみ、End()する。
                // 例外のcatchで先にAbort()していた場合は、このEnd()は無視される。
                transaction.End();
            }
        }
    }
}
```

要点としては、

* End()しない場合は、Abort()される。
* End()/Abort()後にEnd()もしくはAbort()しても無視される。(つまり、早い者勝ち)

ということです。
tryとusingのどちらを外側にするかですが、私が組んでいる限りは例の通りusingが外側の方が多いですね。

# 3. TinyTransactionのソース
例えばPartDocument型はDocument型に自動変換されないため、コンストラクターを各Document型用に用意しました。

```C#
using Inventor;
using System;

namespace InvAddIn
{
    /// <summary>
    /// InventorのTransactionをラップするclassです。
    /// Dispose()でAbort()します。
    /// </summary>
    internal class TinyTransaction : IDisposable
    {
        /// <summary>
        /// Inventorのトランザクションオブジェクトです。
        /// </summary>
        Transaction transaction;
        /// <summary>
        /// Inventorのアプリケーションオブジェクトです。
        /// </summary>
        readonly Application application;
        /// <summary>
        /// トランザクションオブジェクトの生成に必要なdocumentです。
        /// </summary>
        readonly Document document;
        /// <summary>
        /// トランザクション名です。
        /// </summary>
        readonly string transactionName;
        /// <summary>
        /// Dispose()が実行されるとtrueになります。
        /// </summary>
        bool disposed = false;

        /// <summary>
        /// TinyTransactionのコンストラクターです。
        /// </summary>
        /// <param name="inventor">Inventorのアプリケーションオブジェクトです。</param>
        /// <param name="_document">トランザクションオブジェクトの生成に必要なdocumentです。</param>
        /// <param name="name">トランザクション名です。</param>
        /// <param name="deferredStart">直ぐにトランザクションを開始しない場合はtrueに設定します。標準値はfalseです。</param>
        public TinyTransaction(Application inventor, Document _document, string name, bool deferredStart = false)
        {
            application = inventor;
            document = _document;
            transactionName = name;
            if (deferredStart == false)
            {
                Start();
            }
            else
            {
                transaction = null;
            }
        }

        /// <summary>
        /// デストラクターです。トランザクションが実行中であればAbort()してリソースを破棄します。
        /// </summary>
        ~TinyTransaction()
        {
            Dispose(false);
        }

        /// <summary>
        /// Application引数を省略したコンストラクターです。
        /// Applicationのオブジェクトは、documentから得ます。
        /// </summary>
        /// <param name="_document">トランザクションオブジェクトの生成に必要なdocumentです。</param>
        /// <param name="name">トランザクション名です。</param>
        /// <param name="deferredStart">直ぐにトランザクションを開始しない場合はtrueに設定します。標準値はfalseです。</param>
        public TinyTransaction(Document document, string name, bool deferredStart = false) : this((Inventor.Application)document.DocumentEvents.Application, document, name, deferredStart)
        {
        }

        /// <summary>
        /// Application引数を省略したコンストラクターです。
        /// Applicationのオブジェクトは、partDocumentから得ます。
        /// </summary>
        /// <param name="partDocument">トランザクションオブジェクトの生成に必要なdocumentです。</param>
        /// <param name="name">トランザクション名です。</param>
        /// <param name="deferredStart">直ぐにトランザクションを開始しない場合はtrueに設定します。標準値はfalseです。</param>
        public TinyTransaction(PartDocument partDocument, string name, bool deferredStart = false) : this((Document)partDocument, name, deferredStart)
        {
        }

        /// <summary>
        /// Application引数を省略したコンストラクターです。
        /// Applicationのオブジェクトは、assemblyDocumentから得ます。
        /// </summary>
        /// <param name="assemblyDocument">トランザクションオブジェクトの生成に必要なdocumentです。</param>
        /// <param name="name">トランザクション名です。</param>
        /// <param name="deferredStart">直ぐにトランザクションを開始しない場合はtrueに設定します。標準値はfalseです。</param>
        public TinyTransaction(AssemblyDocument assemblyDocument, string name, bool deferredStart = false) : this((Document)assemblyDocument, name, deferredStart)
        {
        }

        /// <summary>
        /// Application引数を省略したコンストラクターです。
        /// Applicationのオブジェクトは、drawingDocumentから得ます。
        /// </summary>
        /// <param name="drawingDocument">トランザクションオブジェクトの生成に必要なdocumentです。</param>
        /// <param name="name">トランザクション名です。</param>
        /// <param name="deferredStart">直ぐにトランザクションを開始しない場合はtrueに設定します。標準値はfalseです。</param>
        public TinyTransaction(DrawingDocument drawingDocument, string name, bool deferredStart = false) : this((Document)drawingDocument, name, deferredStart)
        {
        }

        /// <summary>
        /// トランザクションを開始します。
        /// </summary>
        public void Start()
        {
            if (disposed)
            {
                throw new ObjectDisposedException(GetType().FullName);
            }
            if (transaction != null)
            {
                throw new InvalidOperationException();
            }
            transaction = application.TransactionManager.StartTransaction((_Document)document, transactionName);
        }

        /// <summary>
        /// トランザクションが実行中であれば、tureを返します。
        /// </summary>
        /// <returns>実行中であればtrue。</returns>
        public bool IsRunning()
        {
            if (disposed)
            {
                throw new ObjectDisposedException(GetType().FullName);
            }
            return transaction != null;
        }

        /// <summary>
        /// リソースを破棄します。
        /// トランザクションが実行中であれば、Abort()します。
        /// </summary>
        public void Dispose()
        {
            if (!disposed)
            {
                Dispose(true);
                GC.SuppressFinalize(this);
                disposed = true;
            }
        }

        /// <summary>
        /// リソース破棄の内部処理です。
        /// </summary>
        /// <param name="disposing">Dispose()から呼ぶ場合は、True。デストラクターから呼ぶ場合はfalse。</param>
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                // managedリソースの破棄
            }
            // unmanagedリソースの破棄
            Abort();
        }

        /// <summary>
        /// トランザクションを中止します。
        /// </summary>
        public void Abort()
        {
            if (transaction != null)
            {
                transaction.Abort();
                transaction = null;
            }
        }

        /// <summary>
        /// トランザクションを終了します。
        /// </summary>
        public void End()
        {
            if (transaction != null)
            {
                transaction.End();
                transaction = null;
            }
        }
    }
}
```

# 99. 親の記事に戻る
[Autodesk Inventor API Hacking (概略)](https://qiita.com/yamarah/items/cee08eb8f2b7aecbda83)