#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の使い方
想定される典型的な使い方。
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型用に用意しました。
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 (概略)